flense 0.1.0

Purpose-oriented lensing
Documentation

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.

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);
}