StaticArrays field values printed as undef?

I’m using a StaticArrays.jl FieldVector for a 3-vector that can be both accessed by field name (v.x) as well as by index (v[1]). I also added a length function to get the vector length:

using StaticArrays
using LinearAlgebra

struct vec3 <: FieldVector{3, Float32}
    x::Float32
    y::Float32
    z::Float32
    
    vec3() = zeros(Float32, 3)
    vec3(x, y, z) = new(x, y, z)
end

import Base.length
length(v::vec3) = sqrt(dot(v,v))

v = vec3(0.5f0, 0.0f0, 0.5f0)

println(v)
println(v[1], v.x)
println(length(v))

The import Base.length seems to screw up the printing of v, as I get the following output:

Float32[#undef, #undef, #undef]
0.50.5
0.70710677

The first line suggests the vector fields have an undefined value, but the other two lines show the values are actually correct, they’re merely printed incorrectly. Removing the import Base.length line fixes this and produces:

Float32[0.5, 0.0, 0.5]
0.50.5
0.70710677

Is this an expected side-effect of the import, or a bug?

Base.length should always return the number of elements in an array, breaking this assumption for your own AbstractArray type is expected to cause problems. It looks like you just want norm from LinearAlgebra here?

1 Like

I don’t think this is what you want:

jl> vec3()
3-element Vector{Float32}:
 0.0
 0.0
 0.0

I think you should use

vec3() = new(0f0, 0f0, 0f0)
# or
vec3() = vec3(0f0, 0f0, 0f0)

Ah, didn’t realize vec3 ends up being an AbstractArray, but that makes sense of course.

julia> supertypes(vec3)
(vec3, FieldVector{3,Float32}, FieldArray{Tuple{3},Float32,1}, StaticArray{Tuple{3},Float32,1}, AbstractArray{Float32,1}, Any)

I indeed noticed norm was working anyway as an alternative, but was wondering what was going on here.

So even though I declared vec3() as an inner constructor it can still return any type at all? Ugh.

Thanks to both of you!

Well, you explicitly specified that it should be zeros(Float32, 3), and without passing it through new, so the compiler doesn’t have much choice. Maybe there could be a warning or something.

I think the special thing with inner constructors is that they have access to new, but if you don’t use it, that doesn’t matter. In fact, I don’t think vec3 should be an inner constructor, it should be outer, and just call vec3(0f0,0f0,0f0).

Right, not forgetting to use new() is crucial :slight_smile: . But it’s also a bit surprising that a constructor that is defined within a struct can construct something that is of a different type unrelated to the struct.

One extra thing, though: even if the constructor automatically converted to the correct type, it would still have to go through an ordinary Array first, which would be very inefficient.

Sometimes I’ve seen people construct SArrays by creating an ordinary array and then converting it, like you tried. This should be avoided, always make sure to directly create an SArray.

1 Like

Maybe a construction like this is more interesting:

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

julia> zero(vec3{Float32})
3-element vec3{Float32} with indices SOneTo(3):
 0.0
 0.0
 0.0


Or, if you know that you will never use anything else than Float32, just

julia> struct vec3 <: FieldVector{3,Float32}
           x::Float32
           y::Float32
           z::Float32
       end

julia> zero(vec3)
3-element vec3 with indices SOneTo(3):
 0.0
 0.0
 0.0

Note that that type of structure allows you to write generic functions, like this:

julia> function f(x::Vector{T}) where T
          y = zero(T)
          for v in x
             y += v
          end
          y
       end
f (generic function with 1 method)

julia> x = [ rand(vec3{Float32}) for i in 1:3 ];

julia> f(x)
3-element vec3{Float32} with indices SOneTo(3):
 0.16435432
 1.3771235
 1.274611

julia> x = [ 1, 2, 3 ];

julia> f(x)
6


Thus if you decide to use vec2 or vec4 you don’t need to rewrite any of the functions.