Purpose-oriented lensing for Rust.
Rather than focusing on accessing specific fields of specific structures, `flense` allows a sort of duck-typing for lenses, where you annotate purpose-oriented fields like `Position` or `Color` (or whatever you please!), then define how to adapt the data inside your structure into `flense`.
<details>
<summary>Example of adapting positions in 3D space, like from a Vertex.</summary>
```rust
use flense::prelude::*;
// Some common fields you might wish to use. Maybe you're creating lenses for
// different data structures that might store position in other locations inside
// themselves, or your library wants to access something that is semantically a
// position but to use user-provided data types in processing without requiring
// allocations and transformations back and forth.
enum Position {}
impl Field for Position { type Type = [f32; 3]; }
enum ColorRgb {}
impl Field for ColorRgb { type Type = [u8; 3]; }
enum Normal {}
impl Field for Normal { type Type = [f32; 2]; }
// A plain old data type, defined anywhere - if it was defined outside your lib,
// you could wrap it in a #[repr(transparent)] wrapper and pierce through it.
#[derive(Clone, Copy, Default)]
struct Vertex {
pub position: [f32; 3],
pub color_rgb: [u8; 3],
pub normal: [f32; 2],
}
// Fundamentally, the safety constraint on `Adapter` is: you must provide the
// offset, in bytes, from the beginning of the structure to the adapted field,
// which must be the exact type that is defined in the `Field`.
// In the future, a derive macro might allow annotating structure fields with
// `Field` to derive the correct offset automatically.
unsafe impl Adapter<Position> for Vertex {
const OFFSET: usize = std::mem::offset_of!(Self, position);
}
unsafe impl Adapter<ColorRgb> for Vertex {
const OFFSET: usize = std::mem::offset_of!(Self, color_rgb);
}
unsafe impl Adapter<Normal> for Vertex {
const OFFSET: usize = std::mem::offset_of!(Self, normal);
}
// You can also implement reflexive Adapters; a blanket implementation cannot be
// provided as it would prevent other implementations, since a Field implemented
// remotely could change its type.
// In the future, a derive macro might provide this functionality.
unsafe impl Adapter<Position> for <Position as Field>::Type {
const OFFSET: usize = 0;
}
// Somewhere else, possibly even provided in a library which is unaware of the
// concrete Vertex definition entirely.
fn compute_bounds<'a>(positions: impl LensesSlice<'a, (Position,)>) -> [f32; 3] {
[0.0, 0.0, 0.0] // todo!()
}
// Or, you can avoid performing code generation of the body multiple times by
// separating the lens construction from its usage, though this may prevent
// optimizations like vectorization.
#[inline]
fn compute_bounds_outer<'a>(positions: impl LensesSlice<'a, (Position,)>) -> [f32; 3] {
compute_bounds_inner(positions.lens_slice())
}
fn compute_bounds_inner(positions: LensSlice<(Position,)>) -> [f32; 3] {
// This inner function has no generic parameters whatsoever, but the outer
// function adapts any structure that can be lensed to provide a position!
// This lets you reduce binary size by avoiding monomorphization for every
// type that has a `Position`.
[0.0, 0.0, 0.0] // todo!()
}
fn main() {
let some_vertices = vec![Vertex::default(); 100];
compute_bounds(&*some_vertices);
compute_bounds_outer(&*some_vertices);
let some_contiguous_data = &[[0.0f32, 0.0, 0.0]; 100][..];
compute_bounds(some_contiguous_data);
compute_bounds_outer(some_contiguous_data);
}
```
</details>