Julia: How do you change field values of structs stored in a StructArray?

Viewed 1302

I've tried to create an array of structs, but I've been frustrated (structs seem to lose their fields when put into arrays), so I turned to using StructArrays. This seems to work, but I'm not able to change their fields.

Example code:

# testing arrays of structs
using StructArrays


mutable struct Point
   x :: Float64
   y :: Float64
end


function __init__()

   s = [ Point( rand(), rand() ) for i in 1:10]  # array of 10 structs, with random x,y fields
   sArray = StructArray(s)                       # turn this into a StructArray
   println("\n structArray",sArray,"\n")

   for i = 1:10
      println("\nbefore: ", i," ", sArray[i])
      println("  ]  ",  sArray[i].x)#, z.x)
      print("x was: ", sArray[i].x , "; should be: ",  sArray[i].x /2)
      sArray[i].x /= 2
      sArray[i].y = rand()
      println("\nafter: ", i," ", sArray[i],"\n\n")
   end
end

As you can see from the results below, none of the fields are changing (compare 'before' with 'after'):

before: 1 Point(0.4055949800691312, 0.11581742286491026)
  ]  0.4055949800691312
x was: 0.4055949800691312; should be: 0.2027974900345656
after: 1 Point(0.4055949800691312, 0.11581742286491026)



before: 2 Point(0.8447305147234605, 0.34071870401541093)
  ]  0.8447305147234605
x was: 0.8447305147234605; should be: 0.42236525736173025
after: 2 Point(0.8447305147234605, 0.34071870401541093)
...

Any ideas? I'm not tied to struct arrays, I just thought I would be able to use syntax something like this:

myPoint[1].x = 640
myPoint[1].y = 480
2 Answers

I'm not sure what's wrong with plain arrays of structs (from a usage point of view; maybe your whole point was to have a SoA-like storage), and I'm not sure what you mean by "struct seem to lose their fields when put into arrays":

julia> mutable struct Point
           x::Float64
           y::Float64
       end

       # array of 2 structs, with random x,y fields
julia> s = [ Point(rand(), rand()) for i in 1:2]  
2-element Array{Point,1}:
 Point(0.06669573124277473, 0.8097027646002732)
 Point(0.005831916332594167, 0.18221759177492025)

       # Plain syntax: s[i].fieldname
julia> s[1].x = 42
42

       # s has changed, as expected
julia> s
2-element Array{Point,1}:
 Point(42.0, 0.8097027646002732)
 Point(0.005831916332594167, 0.18221759177492025)


The whole point of StructArrays is to get SoA-like storage, i.e. your entire collection of structures is stored as a set of columns, one for each field in the structures. StructArray provides access to individual rows of the array using a syntax similar to that of an array of structures:

julia> sArray = StructArray(s)
2-element StructArray(::Array{Float64,1}, ::Array{Float64,1}) with eltype Point:
 Point(42.0, 0.8097027646002732)
 Point(0.005831916332594167, 0.18221759177492025)

       # No Point actually exist in sArray; 
       # this is merely a newly constructed Point object
julia> sArray[2]
Point(0.005831916332594167, 0.18221759177492025)

The way it does that is by creating a new Point to store the coordinates, that are copied from the relevant elements in the StructArray fields. If you try to modify these, you're only mutating a copy of the data, not the real data.

One way of mutating the underlying data is to use SoA-like syntax:

       # sArray.y isa Vector{Float64}
       # sArray.y[i] is the y coordinate of the i-th Point
julia> sArray.y[1] = 43
43

       # This way, the sArray is effectively changed
julia> sArray
2-element StructArray(::Array{Float64,1}, ::Array{Float64,1}) with eltype Point:
 Point(42.0, 43.0)
 Point(0.005831916332594167, 0.18221759177492025)

that works! The syntax is non-intuitive (array.x[i] instead of array[i].x), but that might be because I was treating StructArrays as regular arrays without fully reading through how StructArrays work.

I'm new to Julia, with a background in C/C++ and Python. I had been hacking away at the code listed below for several days, before I gave up and tried StructArrays. Oddly, the code below works just fine now, but I had been getting "type Array has no field x" errors. Maybe I just needed to restart the Julia kernel.

function WithArrays()

   myPoints = [Point(0.0, 0.0)  for i=1:10]
   println("\n=> ", myPoints,"\n")
   for i = 1:10
      myPoints[i].x = i*2  #rand()
      myPoints[i].y = i*3  #rand()
   end
   println(myPoints)

end
Related