Confused about type declarations in Julia

Solution 1:

It appears that you are using the @with_kw macro from the Parameters package, so let's start by loading that package:

julia> using Parameters

Now, copying your definition

julia> @with_kw mutable struct NodeLocator{T<:Union{Int64, Vector{Int64}}}
           length::T = 0
           value::T = [0] # changed type declaration here
           maxtreelength::T = 0 end
NodeLocator

we can see that this works fine if we construct a struct with either all ints or all vectors

julia> NodeLocator(0,0,0)
NodeLocator{Int64}
  length: Int64 0
  value: Int64 0
  maxtreelength: Int64 0

julia> NodeLocator([0],[0],[0])
NodeLocator{Vector{Int64}}
  length: Array{Int64}((1,)) [0]
  value: Array{Int64}((1,)) [0]
  maxtreelength: Array{Int64}((1,)) [0]

but fails for mixtures, including the default you have specified (effectively, (0, [0], 0))

julia> NodeLocator([0],[0],0)
ERROR: MethodError: no method matching NodeLocator(::Vector{Int64}, ::Vector{Int64}, ::Int64)
Closest candidates are:
  NodeLocator(::T, ::T, ::T) where T<:Union{Int64, Vector{Int64}} at ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:526
Stacktrace:
 [1] top-level scope
   @ REPL[38]:1

julia> NodeLocator() # use defaults
ERROR: MethodError: no method matching NodeLocator(::Int64, ::Vector{Int64}, ::Int64)
Closest candidates are:
  NodeLocator(::T, ::T, ::T) where T<:Union{Int64, Vector{Int64}} at ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:526
Stacktrace:
 [1] NodeLocator(; length::Int64, value::Vector{Int64}, maxtreelength::Int64)
   @ Main ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:545
 [2] NodeLocator()
   @ Main ~/.julia/packages/Parameters/MK0O4/src/Parameters.jl:545
 [3] top-level scope
   @ REPL[39]:1

This is because as written, you have forced each field of the struct to be the same type T which is in turn a subtype of the union.

If you really want each type to be allowed to be a Union, you could instead write something along the lines of:

julia> @with_kw mutable struct NodeLocator{T<:Int64}
           length::Union{T,Vector{T}} = 0
           value::Union{T,Vector{T}} = [0] # changed type declaration here
           maxtreelength::Union{T,Vector{T}} = 0 end

julia> NodeLocator() # use defaults
NodeLocator{Int64}
  length: Int64 0
  value: Array{Int64}((1,)) [0]
  maxtreelength: Int64 0

However, unless you really need the flexibility for all three fields to be unions, your original definition is actually much preferable. Adding Unions where you don't need them can only slow you down.

One thing that would make the code cleaner and faster would be if you could use a non-mutable struct. This is often possible even when you might not realize it at first, since an Array within a non-mutable struct is still itself mutable, so you can swap out elements within that array without needing the whole struct to be mutable. But there are certainly cases where you do indeed need the mutability -- and if you do need that, go for it.

Finally, I should note that there is not necessarily much point in constraining the numeric type to be T<:Int64 unless that is really the only numeric type you'll ever need. You could easily make your code more general by instead writing something like T<:Integer or T<:Number, which should not generally have any adverse performance costs.