Understanding type Ref{T}

Hello,

I am reading the documentation of the Ref type template: C Interface · The Julia Language

Unfortunately, it doesn’t make sense to me. There are a few examples of use of the Ref type, but I hardly see any value of the use of Ref from these examples.

Let’s look at this one:

julia> isa.(Ref([1,2,3]), [Array, Dict, Int]) # Treat reference values as scalar during broadcasting

I tested it, and then I tested the same statement without the Ref, like that:

isa.([1,2,3], [Array, Dict, Int])

And the result seems to be exactly the same.

Can someone explain what is the purpose of the type Ref and provide an example of use that brings some value compared to not using it ?

Are you sure?

julia> isa.(Ref([1,2,3]), [Array, Dict, Int])
3-element BitVector:
 1
 0
 0

julia> isa.([1,2,3], [Array, Dict, Int])
3-element BitVector:
 0
 0
 1

The first one tests whether [1,2,3] isa Array, then [1,2,3] isa Dict and finally [1,2,3] isa Int.

The latter tests whether 1 isa Array, then 2 isa Array, then 3 isa Int.

1 Like

Oh, I did not watch carefully the results. Thanks for pointing me on this.

So, finally, the Ref type is used to prevent broadcasting from seing and array as an array, but consider it as a single value.

Thanks for this.

Is this the only purpose of Ref, or are there other usages ?

It’s a general single-value container - you can also use it for C interop if your C function expects a pointer.

1 Like

You could also use a Tuple instead of a Ref.

julia> isa.(Ref([1,2,3]), [Array, Dict, Int])
3-element BitVector:
 1
 0
 0

julia> isa.(([1,2,3],), [Array, Dict, Int])
3-element BitVector:
 1
 0
 0

julia> @time isa.(([1,2,3],), (Array, Dict, Int))
  0.000007 seconds (1 allocation: 80 bytes)
(true, false, false)

Using a tuple for the types might end up saving some allocations as well.

Yes, Ref doesn’t seem necessary for this purpose.

From answers so far, I make the hypothesis that Ref is a type dedicated to communication with C and has no practical purpose in pure Julia code.

I hope I am right …

If you need a container with just a single value, e.g. for keeping track of something global in a deep recursion, or some such thing, Ref can be useful. But you may of course use a Vector of length 1 instead.

Thanks for your answer @sgaure

Sorry for asking, but can you put your idea into a short snippet of Julia code so that I can understand the idea ?

Sorry again for being slow at understanding, I am very new to Julia.

That’s a specific case actually. Ref is generally used to let bindings (not sure if this is the right word, I mean variables, fields, elements, etc) share data, to put it colloquially. More formally, it is a mutable container; mutable types are already mutable, so it’s mostly used on immutable types. Despite the name, mutability doesn’t have a monopoly on changing data; reassigning variables e.g. x+=1 does not mutate. However, if you want multiple bindings to see a change at once, you shouldn’t reassign all of them, but provide them with a mutable object, so Ref can help:

julia> x=y=z=1
1

julia> x+=1 # reassigning one variable...
2

julia> x,y,z # ...doesn't reassign the others.
(2, 1, 1)

julia> x=y=z=Ref(1)
Base.RefValue{Int64}(1)

julia> x[] += 1 # mutate the `Ref` by reassigning its element
2

julia> x,y,z
(Base.RefValue{Int64}(2), Base.RefValue{Int64}(2), Base.RefValue{Int64}(2))

Another way to think of Ref is as a 0-dimensional array. It is an array like structure with exactly one element in it. Ref is actually an abstract type, but when used as a constructor it produces a Base.RefValue.

You can otherwise think of it as a pointer to a single element of a specified type.

julia> r = Ref(5)
Base.RefValue{Int64}(5)

julia> r[]
5

julia> ndims(r)
0

julia> size(r)
()

julia> eltype(r)
Int64

julia> r[] = 3
3

julia> r[] = 3.5
ERROR: InexactError: Int64(3.5)
2 Likes