Skip to main content

Soa

Derive Macro Soa 

Source
#[derive(Soa)]
{
    // Attributes available to this derive:
    #[soa]
}
Expand description

Build a fixed sized SoA (Structure of Arrays) from a struct. The outputted SoA will be suitable for in place storage in messages and should be easier for the compiler to vectorize.

for example:

#[derive(Soa)]
struct MyStruct {
   a: i32,
   b: f32,
}

will generate:

pub struct MyStructSoa<const N: usize> {
    pub a: [i32; N],
    pub b: [f32; N],
}

You can then use the generated struct to store multiple instances of the original struct in an SoA format.

// makes an SOA with a default value
let soa1: MyStructSoa<8> = XyzSoa::new(MyStruct{ a: 1, b: 2.3 });

Then you can access the fields of the SoA as slices:

let a = soa1.a();
let b = soa1.b();

You can also access a range of the fields:

let a = soa1.a_range(0..4);
let b = soa1.b_range(0..4);

You can also modify the fields of the SoA:

soa1.a_mut()[0] = 42;
soa1.b_mut()[0] = 42.0;

You can also modify a range of the fields:

soa1.a_range_mut(0..4)[0] = 42;
soa1.b_range_mut(0..4)[0] = 42.0;

You can also apply a function to all the fields of the SoA:

soa1.apply(|a, b| {
   (a + 1, b + 1.0)
});

You can also compose nested SoAs by annotating fields with #[soa(nested)]:

#[derive(Soa)]
struct ColoredPoint {
    #[soa(nested)]
    position: Xyz,
    #[soa(nested)]
    color: Color,
}
// ColoredPointSoa<N> stores position as XyzSoaStorage<N> and color as ColorSoaStorage<N>.

For nested types from other crates, use absolute paths like ::other_crate::Type so the generated storage path resolves correctly. Type aliases are not supported for #[soa(nested)] because storage names are derived from the final type identifier. Bincode encoding changed: SoA fields no longer include per-field length prefixes. Old blobs are not compatible with the new layout.

Memory layout

  • Flat fields: MyStructSoa<N> stores len plus one [T; N] per field.
  • Nested fields (#[soa(nested)]): the field is stored inline as <Field>SoaStorage<N>, so the top-level struct contains len plus nested storage(s) and the leaf arrays live in those nested storages.
  • *SoaStorage<N> has the same layout as *Soa<N> without the len field.

ASCII layouts

Flat:
  MyStructSoa<N>
  +-----+------------------+------------------+
  | len | a: [i32; N]       | b: [f32; N]      |
  +-----+------------------+------------------+

Nested:
  ColoredPointSoa<N>
  +-----+---------------------+---------------------+
  | len | position: XyzSoaStorage<N> | color: ColorSoaStorage<N> |
  +-----+---------------------+---------------------+
               |                               |
               v                               v
       XyzSoaStorage<N>                 ColorSoaStorage<N>
       +------------------+             +------------------+
       | x: [f32; N]       |             | r: [f32; N]       |
       | y: [f32; N]       |             | g: [f32; N]       |
       | z: [f32; N]       |             | b: [f32; N]       |
       | i: [i32; N]       |             +------------------+
       +------------------+
struct ColoredPointSoa<const N: usize> {
    len: usize,
    position: XyzSoaStorage<N>,
    color: ColorSoaStorage<N>,
}
struct XyzSoaStorage<const N: usize> {
    x: [f32; N],
    y: [f32; N],
    z: [f32; N],
    i: [i32; N],
}