Crate soa_derive

source ·
Expand description

This crate provides a custom derive (#[derive(StructOfArray)]) to automatically generate code from a given struct T that allow to replace Vec<T> with a struct of arrays. For example, the following code

#[derive(StructOfArray)]
pub struct Cheese {
    pub smell: f64,
    pub color: (f64, f64, f64),
    pub with_mushrooms: bool,
    pub name: String,
}

will generate a CheeseVec struct that looks like this:

pub struct CheeseVec {
    pub smell: Vec<f64>,
    pub color: Vec<(f64, f64, f64)>,
    pub with_mushrooms: Vec<bool>,
    pub name: Vec<String>,
}

It will also generate the same functions that a Vec<Chees> would have, and a few helper structs: CheeseSlice, CheeseSliceMut, CheeseRef and CheeseRefMut corresponding respectivly to &[Cheese], &mut [Cheese], &Cheese and &mut Cheese.

How to use it

Add #[derive(StructOfArray)] to each struct you want to derive a struct of array version. If you need the helper structs to derive additional traits (such as Debug or PartialEq), you can add an attribute #[soa_derive = "Debug, PartialEq"] to the struct declaration.

#[derive(Debug, PartialEq, StructOfArray)]
#[soa_derive(Debug, PartialEq)]
pub struct Cheese {
    pub smell: f64,
    pub color: (f64, f64, f64),
    pub with_mushrooms: bool,
    pub name: String,
}

If you want to add attribute to a specific generated struct(such as #[cfg_attr(test, derive(PartialEq))] on CheeseVec), you can add an attribute #[soa_attr(Vec, cfg_attr(test, derive(PartialEq)))] to the struct declaration.

#[derive(Debug, PartialEq, StructOfArray)]
#[soa_attr(Vec, cfg_attr(test, derive(PartialEq)))]
pub struct Cheese {
    pub smell: f64,
    pub color: (f64, f64, f64),
    pub with_mushrooms: bool,
    pub name: String,
}

Mappings for first argument of soa_attr to the generated struct for Cheese:

  • Vec => CheeseVec
  • Slice => CheeseSlice
  • SliceMut => CheeseSliceMut
  • Ref => CheeseRef
  • RefMut => CheeseRefMut
  • Ptr => CheesePtr
  • PtrMut => CheesePtrMut

Usage and API

All the generated code have some generated documentation with it, so you should be able to use cargo doc on your crate and see the documentation for all the generated structs and functions.

Most of the time, you should be able to replace Vec<Cheese> by CheeseVec, with exception of code using direct indexing in the vector and a few other caveats listed below.

Caveats and limitations

Vec<T> functionalities rely a lot on references and automatic deref feature, for getting function from [T] and indexing. But the SoA vector (let’s call it CheeseVec, generated from the Cheese struct) generated by this crate can not implement Deref<Target=CheeseSlice>, because Deref is required to return a reference, and CheeseSlice is not a reference. The same applies to Index and IndexMut trait, that can not return CheeseRef/CheeseRefMut.

This means that the we can not index into a CheeseVec, and that a few functions are duplicated, or require a call to as_ref()/as_mut() to change the type used.

Iteration

It is possible to iterate over the values in a CheeseVec

let mut vec = CheeseVec::new();
vec.push(Cheese::new("stilton"));
vec.push(Cheese::new("brie"));

for cheese in vec.iter() {
    // when iterating over a CheeseVec, we load all members from memory
    // in a CheeseRef
    let typeof_cheese: CheeseRef = cheese;
    println!("this is {}, with a smell power of {}", cheese.name, cheese.smell);
}

One of the main advantage of the SoA layout is to be able to only load some fields from memory when iterating over the vector. In order to do so, one can manually pick the needed fields:

for name in &vec.name {
    // We get referenes to the names
    let typeof_name: &String = name;
    println!("got cheese {}", name);
}

In order to iterate over multiple fields at the same time, one can use the soa_zip! macro.

for (name, smell, color) in soa_zip!(&mut vec, [name, mut smell, color]) {
    println!("this is {}, with color {:#?}", name, color);
    // smell is a mutable reference
    *smell += 1.0;
}

Nested Struct of Arrays

In order to nest a struct of arrays inside another struct of arrays, one can use the #[nested_soa] attribute.

For example, the following code

#[derive(StructOfArray)]
pub struct Point {
    x: f32,
    y: f32,
}
#[derive(StructOfArray)]
pub struct Particle {
    #[nested_soa]
    point: Point,
    mass: f32,
}

will generate structs that looks like this:

pub struct PointVec {
    x: Vec<f32>,
    y: Vec<f32>,
}
pub struct ParticleVec {
    point: PointVec, // rather than Vec<Point>
    mass: Vec<f32>
}

All helper structs will be also nested, for example PointSlice will be nested in ParticleSlice.

Macros

  • Create an iterator over multiple fields in a Struct of array style vector.

Traits

  • Helper trait used for indexing operations. Inspired by std::slice::SliceIndex.
  • Helper trait used for indexing operations returning mutable references. Inspired by std::slice::SliceIndex.
  • Any struct derived by StructOfArray will auto impl this trait.
  • Any struct derived by StructOfArray will auto impl this trait You can use <Cheese as StructOfArray>::Type instead of explicit named type CheeseVec; This will helpful in generics programing that generate struct can be expressed as <T as StructOfArray>::Type

Derive Macros