# Subtyping FieldVector from StaticArrays.jl

**URL:** <https://discourse.julialang.org/t/subtyping-fieldvector-from-staticarrays-jl/127312>\
**Category:** General Usage\
**Tags:** question, parametric-types, staticarrays\
**Created:** [March 24, 2025, 4:41pm UTC](https://discourse.julialang.org/t/subtyping-fieldvector-from-staticarrays-jl/127312 "2025-03-24T16:41:53Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [March 24, 2025, 4:41pm UTC](https://discourse.julialang.org/t/subtyping-fieldvector-from-staticarrays-jl/127312/1 "2025-03-24T16:41:53Z")

</div>

I am using this approach in a couple of projects:

```julia
julia> import StaticArrays: FieldVector, Size, similar_type

julia> struct Direction{T<:AbstractFloat} <: FieldVector{3, T}
           x::T
           y::T
           z::T
       end

julia> similar_type(::Type{<:Direction}, ::Type{T}, s::Size{(3,)}) where {T} = Direction{T}
similar_type (generic function with 21 methods)

julia> Direction(1.0, 0, 0)
3-element Direction{Float64} with indices SOneTo(3):
 1.0
 0.0
 0.0

julia> Direction(1.0, 0, 0) / 2
3-element Direction{Float64} with indices SOneTo(3):
 0.5
 0.0
 0.0

```

where I want to enforce that the type is a subtype of `AbstractFloat`. Everything is fine so far, the type is parametric and the parameter type can be propagated to other types which uses this type as field type.

The problem I encounter with this approach is on the user side, namely the error message which is confusing:

```julia
julia> Direction(1, 0, 0)
ERROR: ArgumentError: cannot construct a value of type Union{} for return result
Stacktrace:
 [1] (::Core.TypeofBottom)(a::Tuple{Int64, Int64, Int64})
   @ Core ./boot.jl:318
 [2] Direction(::Int64, ::Vararg{Int64})
   @ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/convert.jl:173
 [3] top-level scope
   @ REPL[11]:1

```

I first thought I can easily solve this with a generic method like:

```julia
julia> Direction(_, _, _) = throw(ArgumentError("All elements must be convertible to an AbstractFloat"))
Direction

julia> Direction(1, 0, 0)
ERROR: ArgumentError: All elements must be convertible to an AbstractFloat
Stacktrace:
 [1] Direction(::Int64, ::Int64, ::Int64)
   @ Main ./REPL[19]:1
 [2] top-level scope
   @ REPL[20]:1

julia> Direction(1.0, 0, 0)
ERROR: ArgumentError: All elements must be convertible to an AbstractFloat
Stacktrace:
 [1] Direction(::Float64, ::Int64, ::Int64)
   @ Main ./REPL[19]:1
 [2] top-level scope
   @ REPL[21]:1

```

but as seen above, it ignores the (in my view) more specific method in this case.

I also tried to replace the inner constructor and check the types there, which almost works, but as seen below, now the parametric constructor is gone 😆 So when I use already existing objects, the “type propagation” fails

```julia
julia> struct Direction{T<:AbstractFloat} <: FieldVector{3, T}
           x::T
           y::T
           z::T
           function Direction(x, y, z)
               T = promote_type(typeof(x), typeof(y), typeof(z))
               if !(T <: AbstractFloat)
                   throw(ArgumentError("All elements must be convertible to an AbstractFloat"))
               end
               new{T}(x, y, z)
           end
       end

julia> similar_type(::Type{<:Direction}, ::Type{T}, s::Size{(3,)}) where {T} = Direction{T}
similar_type (generic function with 21 methods)

julia> Direction(1, 0, 0)
ERROR: ArgumentError: All elements must be convertible to an AbstractFloat
Stacktrace:
 [1] Direction(x::Int64, y::Int64, z::Int64)
   @ Main ./REPL[2]:8
 [2] top-level scope
   @ REPL[3]:1

julia> Direction(1.0, 0, 0)
3-element Direction{Float64} with indices SOneTo(3):
 1.0
 0.0
 0.0

julia> Direction(1.0, 0, 0) / 2
ERROR: The constructor for Direction{Float64}(::Float64, ::Float64, ::Float64) is missing!
Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:35
  [2] _missing_fa_constructor(FA::Any, AT::Any)
    @ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/FieldArray.jl:13
  [3] construct_type(::Type{Direction{Float64}}, x::StaticArrays.Args{Tuple{Float64, Float64, Float64}})
    @ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/FieldArray.jl:8
  [4] StaticArray
    @ ~/.julia/packages/StaticArrays/2dZx4/src/convert.jl:173 [inlined]
  [5] FieldArray
    @ ~/.julia/packages/StaticArrays/2dZx4/src/FieldArray.jl:2 [inlined]
  [6] macro expansion
    @ ~/.julia/packages/StaticArrays/2dZx4/src/mapreduce.jl:78 [inlined]
  [7] _map
    @ ~/.julia/packages/StaticArrays/2dZx4/src/mapreduce.jl:42 [inlined]
  [8] map
    @ ~/.julia/packages/StaticArrays/2dZx4/src/mapreduce.jl:33 [inlined]
  [9] /(a::Direction{Float64}, b::Int64)
    @ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/linalg.jl:24
 [10] top-level scope
    @ REPL[5]:1

```

I am not sure how to solve this in an elegant way and I am currently stuck. I read through a couple of StaticArray topics but none of my endeavours were successful.

Maybe I am overcomplicating things and cat’t see the forest for the trees 😉

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [March 25, 2025, 3:04pm UTC](https://discourse.julialang.org/t/subtyping-fieldvector-from-staticarrays-jl/127312/2 "2025-03-25T15:04:49Z")

</div>

I think I solved with this:

```julia
struct Direction{T<:AbstractFloat} <: FieldVector{3, T}
    x::T
    y::T
    z::T
    function Direction{U}(x, y, z) where U
        T = promote_type(typeof(x), typeof(y), typeof(z), U)
        if !(T <: AbstractFloat)
            throw(ArgumentError("All elements must be convertible to an AbstractFloat"))
        end
        new{T}(x, y, z)
    end
end
similar_type(::Type{<:Direction}, ::Type{T}, s::Size{(3,)}) where {T} = Direction{T}

```

not sure if it’s the best way though. Also not sure what kind of downstream errors this will produce 😉

Edit: to early, now this does not work anymore:

```julia
julia> Direction(1, 2, 3)
ERROR: ArgumentError: cannot construct a value of type Union{} for return result
Stacktrace:
 [1] (::Core.TypeofBottom)(a::Tuple{Int64, Int64, Int64})
   @ Core ./boot.jl:318
 [2] Direction(::Int64, ::Vararg{Int64})
   @ StaticArrays ~/.julia/packages/StaticArrays/2dZx4/src/convert.jl:173
 [3] top-level scope
   @ REPL[4]:1

```

---

<div class="post-metadata">

**Author:** ![tamasgal](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamasgal/32/27946_2.png) [@tamasgal](https://discourse.julialang.org/u/tamasgal)\
**Post date:** [March 25, 2025, 3:12pm UTC](https://discourse.julialang.org/t/subtyping-fieldvector-from-staticarrays-jl/127312/3 "2025-03-25T15:12:18Z")

</div>

OK this is it (not really happy because there is no explicit conversion for e.g. only integer type parameters, but at least the error makes a bit more sense now). As usual, I was overthinking it…

```julia
struct Direction{T<:AbstractFloat} <: FieldVector{3, T}
    x::T
    y::T
    z::T
end
function Direction(x, y, z)
    T = promote_type(typeof(x), typeof(y), typeof(z))
    if !(T <: AbstractFloat)
       throw(ArgumentError("All elements must be convertible to an AbstractFloat"))
    end
    Direction{T}(x, y, z)
end
similar_type(::Type{<:Direction}, ::Type{T}, s::Size{(3,)}) where {T} = Direction{T}

```
