Help understanding dot assignment to Vector of mutable static arrays

I am puzzled by the following behavior, which produced a difficult to find bug…

julia> using StaticArrays

julia> t = @MVector([1.0,2.0])
2-element MVector{2, Float64} with indices SOneTo(2):
 1.0
 2.0

julia> x = zeros(typeof(t),5)
5-element Vector{MVector{2, Float64}}:
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]

julia> x[1] .= t
2-element MVector{2, Float64} with indices SOneTo(2):
 1.0
 2.0

julia> x
5-element Vector{MVector{2, Float64}}:
 [1.0, 2.0]
 [1.0, 2.0]
 [1.0, 2.0]
 [1.0, 2.0]
 [1.0, 2.0]

julia> x[1] .= 0
2-element MVector{2, Float64} with indices SOneTo(2):
 0.0
 0.0

julia> x
5-element Vector{MVector{2, Float64}}:
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]

julia> x[1] = t
2-element MVector{2, Float64} with indices SOneTo(2):
 1.0
 2.0

julia> x
5-element Vector{MVector{2, Float64}}:
 [1.0, 2.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]

Could someone please explain why x[1] .= t replaces all contents of x? I expected it to replace only the contents of x[1].

1 Like

You’ve found a bug with StaticArrays. Specifically, x = zeros(typeof(t),5) produces a Vector with 5 of the same object, instead of 5 copies of the MVector

1 Like

zeros uses fill!, which does not copy arrays. The docstring says “If x is an object reference, all elements will refer to the same object”. All StaticArrays does is define zero(::MVector). I’m not sure if this is a bug in StaticArrays or a limitation of zeros in general for array types.

In this particular example it might be better to use comprehension.

julia> x = [zero(typeof(t)) for _ in 1:5]
5-element Vector{MVector{2, Float64}}:
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]

julia> x[1] .= t
2-element MVector{2, Float64} with indices SOneTo(2):
 1.0
 2.0

julia> x
5-element Vector{MVector{2, Float64}}:
 [1.0, 2.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
 [0.0, 0.0]
1 Like

While others have answered your question (every index of your array is the exact same vector, so mutate one and “all” change because they’re really the same one), a suggestion on your code is that you would likely get better performance using a vector of SVectors or HybridArrays, as either of these would be one contiguous block of memory, instead of many scattereed pieces.

Somethings would be faster with the vector of (M)vector approach though. Loading/storing a vector would be cheap, so if you do things like swapping them, it may be faster with MVectors.
Of course, setting every element to 0 is fastest in your situation, where multiple indices point to the same one. Or if you do more generally want their contents to be updatable via spooky at-a-distance action like you have here.
But for most use cases, the vector of svector is or HybridArrays will probably be faster.

3 Likes

This is something that causes confusion quite often, and the resulting bugs are hard to track.

Is there any actual known important use of fill that relies on this, or changing this behavior can be considered? (Or simply throwing a method error)

Thanks for all the answers. I was aware of the dangers of fill but I did not realize that zeros calls fill. I do not see this mentioned in the documentation for zeros, though there is a “see also” pointing to fill. However, I had interpreted that reference to be merely a suggestion for another possible way to create a zero matrix. @elrod: I will replace MVector with SVector in my code, thanks for pointing this out.

It is just an implementation detail, types can implement zeros any way they prefer.

FWIW, if zeros(T, ...) where T is mutable is one of those corner cases where it is hard to decide what to do because both options make sense in some context. It is better to be explicit in code.