# Union type in struct: type conversion

As a minimum example, one field in my struct can be a complex-valued matrix or vector:

``````struct foo_1
a::VecOrMat{ComplexF64}
end

VecOrMat{ComplexF64}(x::Vector) = Vector{ComplexF64}(x)
VecOrMat{ComplexF64}(x::Matrix) = Matrix{ComplexF64}(x)
``````

which allows the input to be, e.g. an integer vector, and convert it to complex floating vector:

``````foo_1([1, 2])
``````

foo_1(Complex{Float64}[1.0 + 0.0im, 2.0 + 0.0im])

This works as expected; however, another field in my struct can be a floating-point number or vector of such:

``````struct foo_2
x::Union{Float64, Vector{Float64}}
end

Union{Float64, Vector{Float64}}(x::Number) = Float64(x)
Union{Float64, Vector{Float64}}(x::Vector) = Vector{Float64}(x)
``````

This however errors:

``````foo_2(1)
``````

MethodError: Cannot `convert` an object of type
Int64 to an object of type
Union{Float64, Array{Float64,1}}
Closest candidates are:
convert(::Type{T}, !Matched::T) where T at essentials.jl:171
Union{Float64, Array{Float64,1}}(::Number) at In[3]:5

Stacktrace:
[1] foo_2(::Int64) at ./In[3]:2
[2] top-level scope at In[6]:1

given `VecOrMat` is just `Union{Array{T,1}, Array{T,2}} where T`, why is be behavior of `foo_2` different from `foo_1`? `@code_typed` shows

``````@code_typed foo_1([1, 2])
``````
``````CodeInfo(
1 β %1 = Main.foo_1::Core.Compiler.Const(foo_1, false)
β   %2 = Core.fieldtype(%1, 1)::Type{Union{Array{Complex{Float64},1}, Array{Complex{Float64},2}}}
β   %3 = invoke Base.convert(%2::Type{Union{Array{Complex{Float64},1}, Array{Complex{Float64},2}}}, _2::Array{Int64,1})::Any
β   %4 = %new(%1, %3)::foo_1
βββ      return %4
) => foo_1
``````

and

``````@code_typed foo_2([1, 2])
``````
``````CodeInfo(
1 β %1 = Main.foo_2::Core.Compiler.Const(foo_2, false)
β   %2 = Core.fieldtype(%1, 1)::Type{Union{Float64, Array{Float64,1}}}
β        Base.convert(%2, x)::Union{}
βββ      \$(Expr(:unreachable))::Union{}
) => Union{}
``````

I could overload `Base.convert()` to make `foo_2` work,

``````import Base.convert
convert(::Type{Union{Float64, Vector{Float64}}}, x::Number) = Float64(x)
convert(::Type{Union{Float64, Vector{Float64}}}, x::Vector) = Vector{Float64}(x)
``````

but still wondering the reason why the error happens, and whatβs a better way to deal with this error? Thanks!

1 Like

You should make a parametric type.

(And the convention is to have type names that start with a capital letter.)

``````struct Foo1{T <: VecOrMat{ComplexF64}}
a::T
end
``````
3 Likes

@dpsanders recommendation is a good one if you want to specialize based on which type is in the field. There are a few cases where you donβt want to do that, so if you want to continue with your design then `@code_typed` gives you a clue how to fix it:

``````julia> @code_typed foo_1([1, 2])
CodeInfo(
1 β %1 = Main.foo_1::Core.Compiler.Const(foo_1, false)
β   %2 = invoke Union{Array{Complex{Float64},1}, Array{Complex{Float64},2}}(_2::Array{Int64,1})::Any
β   %3 = %new(%1, %2)::foo_1
βββ      return %3
) => foo_1
``````

You can see that the `invoke` is calling the methods you defined.

In contrast,

``````julia> @code_typed foo_2(1)
CodeInfo(
1 β %1 = Main.foo_2::Core.Compiler.Const(foo_2, false)
β   %2 = Core.fieldtype(%1, 1)::Type{Union{Float64, Array{Float64,1}}}
β        Base.convert(%2, x)::Union{}
βββ      unreachable
) => Union{}
``````

calls `Base.convert`. So you should define the `convert` method in addition to/instead of the ones you defined:

``````julia> Base.convert(::Type{Union{Float64, Vector{Float64}}}, x::Number) = Float64(x)

julia> Base.convert(::Type{Union{Float64, Vector{Float64}}}, x::Vector) = Vector{Float64}(x)

julia> foo_2(1)
foo_2(1.0)

``````

To be honest Iβm not sure why these two differ.

3 Likes

So you should define the `convert` method in addition to/instead of the ones you defined:

``````julia> Base.convert(::Type{Union{Float64, Vector{Float64}}}, x::Number) = Float64(x)
julia> Base.convert(::Type{Union{Float64, Vector{Float64}}}, x::Vector) = Vector{Float64}(x)
``````

Sorry to revive this thread, but is there any downside to overriding Base.convert in this way? These particular definitions seem benign, but e.g. this Github issue seems to suggest that overriding Base.convert is a bad idea (βtype piracyβ).

``````VecOrMat{ComplexF64}(x::Vector) = Vector{ComplexF64}(x)
is this considered type piracy, since it modifies the behavior of `VecOrMat`?