Macro optics::declare_lens

source ·
macro_rules! declare_lens {
    (
        $(#[$m:meta])* $vis:vis
        $name:ident as $base:ty => $target:ty $(, for<$($p:ident),+ $(,)?>)?,
        ($s:ident) => by_val: $by_val:expr, by_ref: $by_ref:expr, by_mut: $by_mut:expr $(,)?
    ) => { ... };
    (
        $(#[$m:meta])* $vis:vis
        $name:ident as $base:ty => $target:ty $(, for<$($p:ident),+ $(,)?>)?,
        ($s:ident) $(reused($wrap:ident))? => $reused:expr $(,)?
    ) => { ... };
}
Expand description

Declare a Lens from an accessor expression.

Since we have mutability built into the language, we can make use of it, and Getter plus GetterRef gives us full capabilities of the Lens interface.

Here is an example usage. The match expression below is reused 3 times in Getter::view, GetterRef::view_ref, and GetterMut::view_mut; this relies on the fact that (shared, mutable) borrow of fields is implicit in match expressions.

#[derive(Debug, Copy, Clone, PartialEq)]
enum Color {
    Rgba { red: f32, green: f32, blue: f32, alpha: f32 },
    Hsla { hue: f32, saturation: f32, lightness: f32, alpha: f32 },
}

declare_lens! {
    /// Alpha component for colors.
    _Alpha as Color => f32,
    (c) => match c {
        Color::Rgba { alpha, .. } |
        Color::Hsla { alpha, .. } => alpha,
    }
}

let mut color = Color::Rgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 };
// Getter(Ref,Mut)
assert_eq!(_Alpha.view(color), 1.0);
assert_eq!(_Alpha.view_ref(&color), &1.0);
assert_eq!(_Alpha.view_mut(&mut color), &mut 1.0);
// AffineFold(Ref,Mut)
assert_eq!(_Alpha.preview(color), Ok(1.0));
assert_eq!(_Alpha.preview_ref(&color), Ok(&1.0));
assert_eq!(_Alpha.preview_mut(&mut color), Ok(&mut 1.0));
// Setter
_Alpha.over(&mut color, &mut |alpha| *alpha *= 0.5);
assert_eq!(color, Color::Rgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5 });
// AffineTraversal
_Alpha.set(&mut color, 0.0);
assert_eq!(color, Color::Rgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0 });

It is also possible to define the normal “field accessor” lenses with this macro, but whenever possible, one should prefer declare_lens_from_field for simplicity.

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Foo { a: u32, b: bool, c: char }

declare_lens! {
    /// Lens for `Foo::c`.
    FooC as Foo => char, (foo) =>
    by_val: foo.c,
    by_ref: &foo.c,
    by_mut: &mut foo.c,
}

let mut foo = Foo { a: 42, b: true, c: 'A' };
// Getter(Ref,Mut)
assert_eq!(FooC.view(foo), 'A');
assert_eq!(FooC.view_ref(&foo), &'A');
assert_eq!(FooC.view_mut(&mut foo), &mut 'A');
// AffineFold(Ref,Mut)
assert_eq!(FooC.preview(foo), Ok('A'));
assert_eq!(FooC.preview_ref(&foo), Ok(&'A'));
assert_eq!(FooC.preview_mut(&mut foo), Ok(&mut 'A'));
// Setter
FooC.over(&mut foo, &mut |c| c.make_ascii_lowercase());
assert_eq!(foo, Foo { a: 42, b: true, c: 'a' });
// AffineTraversal
FooC.set(&mut foo, 'X');
assert_eq!(foo, Foo { a: 42, b: true, c: 'X' });

To reuse the accessor expression to some extent, we can also use the reused(wrap) syntax:

declare_lens! {
    /// Lens for `Foo::c`.
    FooC as Foo => char,
    (foo) reused(wrap) => wrap!(foo.c)
}

The expression wrap!(x) will be replaced by x, &x, and finally &mut x. One can also use different identifiers other than wrap.