 # 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:5

Stacktrace:
 foo_2(::Int64) at ./In:2
 top-level scope at In: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!

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