Difference of type covariant in between struct and function

Hello, let’s define a few type hierarchy (on Julia v0.7):

julia> abstract type T end

julia> abstract type T1 <: T end

julia> abstract type T2 <: T end

For the function, it works:

julia> foo2(x::X) where {X<:Type{<:T}} = "ok"
foo2 (generic function with 1 method)

julia> foo2(T1)
"ok"

julia> foo2(T)
"ok"

However, if I construct a struct it won’t works,

julia> struct Foo{X<:Type{<:T}}
       x::X
       end

julia> Foo(T1)
ERROR: TypeError: in Foo, in X, expected X<:(Type{#s12} where #s12<:T), got Type{DataType}
Stacktrace:
 [1] Foo(::Type{T1}) at ./REPL[5]:2
 [2] top-level scope at none:0

Then I use the inner constructor which is nearly the same as the function and still doesn’t work:

julia> struct Foo2{X<:Type{<:T}}
       x::X
       function Foo2(x::X) where {X<:Type{<:T}}
              s = new{x}()
              s.x = x
       end
       end

julia> Foo2(T1)
ERROR: TypeError: in Foo2, in X, expected X<:(Type{#s13} where #s13<:T), got Type{T1}
Stacktrace:
 [1] Foo2(::Type{T1}) at ./REPL[17]:4
 [2] top-level scope at none:0

Why wouldn’t my T1 be passed into Foo or Foo2? One reason I would do this is trying to dispatch specifically on T1 and T2 but also need some generality on T somehow.

Thank you!

There’s no difference between the acceptable type parameters, the only difference is in type parameter binding in method dispatch. The default constructor is Foo(x::X) where X and in this case X will only be typeof(X) unless it is written as Foo(x::Type{...}) .... Foo{Type{T1}}(T1) works.

Firstly, it should be new{X}. Secondly, the constraint on X does not change what X binds to, it still binds to DataType. You need to change it to sth like Foo2(x::Type{X}) where X <: T. Lastly, Foo2 is immutable, you cannot assign to a field after consturction.

julia> abstract type T end

julia> abstract type T1 <: T end

julia> abstract type T2 <: T end

julia> struct Foo3{X<:Type{<:T}}
       x::X
       function Foo3(x::Type{X}) where {X<:T}
              s = new{Type{X}}(x)
       end
       end

julia> Foo3(T1)
Foo3{Type{T1}}(T1)
2 Likes

And finally, you can figure out these all by yourself even without knowing much about the dispatch rule by just checking the value of x and X in the constructor. It should be more clear at that point what went wrong…