Wrapper{Type{Int}} really needed?

Julia’s documentation recommends to wrap types into Type{...} for performance considerations

The concrete example in the documentation is

julia> struct WrapType{T}
       value::T
       end

julia> WrapType(Float64) # default constructor, note DataType
WrapType{DataType}(Float64)

julia> WrapType(::Type{T}) where T = WrapType{Type{T}}(T)
WrapType

julia> WrapType(Float64) # sharpened constructor, note more precise Type{Float64}
WrapType{Type{Float64}}(Float64)

However, trying everything with a plain Wrapper which does not do this Type{...} support, inference seems to work fully.

julia> struct Wrapper{T}
           value::T
       end

julia> function f()
           a = Wrapper(Int)
           a.value
       end
f (generic function with 1 method)

julia> Wrapper(Int)
Wrapper{DataType}(Int64)

julia> Base.promote_op(f)
Type{Int64}

Is the example in the documentation outdated? Do all Types have by now extra support for type-inference such that they do not need to be marked with Type{...} any longer?

That’s likely just constant folding in the specific case. If you add a Wrapper argument to f, the inference fails as expected:

julia> function f(b::Wrapper)
           a = Wrapper(Int)
           promote_type(a.value, b.value)
       end
f (generic function with 2 methods)

julia> Base.promote_op(f, typeof(Wrapper(Float64)))
Type{S} where S
5 Likes

Thank you very much for the concise failing example.

Can you even construct an example where it does not depend on passing in typeof(Wrapper(Float64)) to promote_op?
I mean when does Julia’s constant-folding mechanism stop working and falls back to type-level inference only?

Somewhat contrived

function foo()
    if rand(Bool)
        a = Wrapper(Int)
    else
        a = Wrapper(Vector{Int})
    end
    a.value
end

Now Base.promote_op(foo) returns DataType and not Union{Type{Int}, Type{Vector{Int}}}. If a does not have multiple bind sites, I’m unsure how complex the remaining function must be for constant folding not working. However, constant folding and type inference in general is a compiler optimization not affecting the semantics, so it may change from version to version.

2 Likes

A much more concise (and deterministic!) example:

function foo()
    a = Ref(Wrapper(Int))
    a[].value
end

julia> Base.promote_op(foo)
DataType
1 Like

thank you both very much for these additional examples.
It helps a lot to see the inference failing in real simple examples