Why are ref's in structs type unstable?

Hello Julia Community,

I’ve just came across a small code example which isn’t type stable according to @code_warntype but clearly should be, at least according to my understanding of refs.

Here is the example:

julia> struct Foo
           bar :: Ref{Int}
       end

julia> increment(foo) = foo.bar[] += 1
increment (generic function with 1 method)

julia> @code_warntype increment(Foo(Ref(0)))
Variables
  #self#::Core.Const(increment)
  foo::Foo

Body::Any
1 ─ %1 = Base.getproperty(foo, :bar)::Ref{Int64}
│   %2 = Base.getindex(%1)::Any
│   %3 = (%2 + 1)::Any
│   %4 = Base.getproperty(foo, :bar)::Ref{Int64}
│        Base.setindex!(%4, %3)
└──      return %3

Is this intended and a misunderstanding on my side or this an unintended bug?

Using @edit getindex(Ref(0)) shows that the definition of getindex doesnt specialize on the eltype.
So I could just roll my own Ref implementation, but is this really intended? Was this a decision to reduce compilation times?

(I was using 1.6.0 but it still seems to be there in 1.7.2)

Ref{Int} is an abstract type. Ref(0) creates a Base.RefValue{Int}, which is a concrete subtype of Base.Ref{Int}:

julia> isconcretetype(Ref{Int})
false

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

julia> isconcretetype(Base.RefValue{Int64})
true

Therefore, the struct field should have the type Base.RefValue{Int} instead of Ref{Int} if you want type stability.

3 Likes

Oh, TIL!

The next question of course is: why do we have a Ref{T} and a RefValue{T}? Why not just RefValue{T}?

Because there are more kinds of Refs beside RefValues:

julia> subtypes(Ref)
6-element Vector{Any}:
 Base.CFunction
 Base.RefArray
 Base.RefValue
 Core.Compiler.RefValue
 Core.LLVMPtr
 Ptr
2 Likes

RefArray in particular is an underrated gem!

2 Likes