Why is the first function below type stable and the second not?
test( rv::Ref{V} ) where V = rv[]
@code_warntype test( Ref(0x1) )
struct T{V}
root::Ref{V}
end
t = T( Ref(0x1) )
test( t::T{V} ) where V = t.root[]
@code_warntype test( t )
Ref being an abstract type is not enough to explain the difference. The crucial bit that’s missing is that Julia will specialize function calls to test(rv::Ref{V}) according to the actual type of the object being passed in. So when you pass in a Base.RefValue, test is compiled for that specific type, which means that there is no type instability in the first place.
In the case of your struct though, Julia doesn’t have that information available - when the test function is compiled for the struct argument, all that is known about the root field is the abstract type of the field, not the type of the actual object that’s stored there. Hence, the compiler can’t assume anything more specific and inserts a dynamic dispatch. After all, any object with a type that’s a subtype of Ref may be stored at that field, not just Base.RefValue.
The fact that this confusion comes up in the first place is quite unfortunate. Ref is an abstract type, documented to always refer to valid, julia-allocated memory. It’s also the supertype of Ptr, which may be coming from outside of Julia! This doesn’t really make much sense.. See `Ptr` doesn't observe the supertype semantics of `Ref` · Issue #49004 · JuliaLang/julia · GitHub where this is tracked upstream.