pas (\pa\), meaning "step" in French, is a crate for slicing stuff, especially strided data.
pas allows you to:
- Get a slice with a custom stride
- Slice only a part of a struct
- This operation is endian dependant
- No mechanism to encode/decode to/from big endian is provided
Using slice_attr!
to slice in a struct
and automatically infer the type:
use pas::{slice, slice_attr};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
pub position: [f32; 3],
pub uv: [f32; 2],
}
fn main() {
let vertices = [
Vertex {position: [1.0, 0.5, 1.0], uv: [1.0, 1.0]},
Vertex {position: [1.0, 1.0, 0.5], uv: [0.0, 1.0]},
];
// Start slice at first vertex, pointing at `position`.
let positions = slice_attr!(vertices, [0].position);
println!("{:?}", positions); // [[1.0, 0.5, 1.0], [1.0, 1.0, 0.5]]
// Start slice at second vertex, pointing at `uv`.
let uvs = slice_attr!(vertices, [1].uv);
println!("{:?}", uvs); // [[0.0, 1.0]]
}
It can be useful to slice at a struct
attribute, but with a smaller type:
let x_positions: Slice<f32> = slice!(vertices, [0].position[0]);
println!("{:?}", x_positions); // [1.0, 1.0]
let y_positions: Slice<f32> = slice!(vertices, [0].position[1]);
println!("{:?}", y_positions); // [0.5, 1.0]
let z_positions: Slice<f32> = slice!(vertices, [0].position[2]);
println!("{:?}", z_positions); // [1.0, 0.5]
When slicing an array whose type information is known only at runtime, you can use Slice
/SliceMut
:
let uv_byte_offset = std::mem::size_of::<Vertex>() + std::mem::size_of::<[f32; 3]>();
// Slice starting at the byte offset `32`, with a stride of 1 element.
let uvs: Slice<[f32; 3]> = Slice::new(&vertices, uv_byte_offset);
println!("{:?}", uvs); // [[0.0, 1.0]]
It's possible to use a custom stride, in elements count:
use pas::{slice_attr, Slice};
let data: [u32; 5] = [0, 1, 2, 3, 4];
// Using the macro, the stride appears first
let slice = slice_attr!(2, data, [0]);
println!("{:?}", slice); // [0, 2, 4]
// Specified as the last argument when using `Slice`/`SliceMut`
let slice: Slice<u32> = Slice::strided(&data, 0, 3);
println!("{:?}", slice); // [0, 3]
The stride must always be at least the size of the attribute. This example will panic:
use pas::{slice_attr, Slice};
let data: [u32; 5] = [0, 1, 2, 3, 4];
// Default stride is `std::mem::size_of::<u32>()` here, attribute
// size is `std::mem::size_of::<[u32; 3]>()`.
let _: Slice<[u32; 3]> = Slice::new(&data, 0);
While this crate makes use of unsafe
and transmute
, it's (mostly) safe
to use and comes with runtime checks preventing you to run into undefined behaviors:
- Ensure that reads are aligned
- Check size of read compared to stride
This crate requires your types to implement the Pod trait from the bytemuck crate, improving safety with alignment rules, and illegal bit patterns.