Skip to main content

lens/
core.rs

1//
2// Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc.
3// All rights reserved.
4//
5//
6// Copyright (c) 2025 Julius Foitzik on derivative work
7// All rights reserved.
8//
9
10use std::marker::PhantomData;
11
12pub use crate::path::LensPath;
13
14/// A lens offers a purely functional means to access and/or modify a field that is
15/// nested in an immutable data structure.
16pub trait Lens {
17    /// The lens source type, i.e., the object containing the field.
18    type Source;
19
20    /// The lens target type, i.e., the field to be accessed or modified.
21    type Target;
22
23    /// Returns a `LensPath` that describes the target of this lens relative to its source.
24    fn path(&self) -> LensPath;
25
26    /// Sets the target of the lens. (This requires a mutable source reference, and as such is typically
27    /// only used internally.)
28    #[doc(hidden)]
29    fn mutate(&self, source: &mut Self::Source, target: Self::Target);
30
31    /// Sets the target of the lens and returns the new state of the source. (This consumes the source.)
32    fn set(&self, source: Self::Source, target: Self::Target) -> Self::Source {
33        let mut mutable_source = source;
34        self.mutate(&mut mutable_source, target);
35        mutable_source
36    }
37}
38
39/// A lens that allows the target to be accessed and mutated by reference.
40pub trait RefLens: Lens {
41    /// Gets a reference to the target of the lens. (This does not consume the source.)
42    fn get_ref<'a>(&self, source: &'a Self::Source) -> &'a Self::Target;
43
44    /// Gets a mutable reference to the target of the lens. (This requires a mutable source reference,
45    /// and as such is typically only used internally.)
46    #[doc(hidden)]
47    fn get_mut_ref<'a>(&self, source: &'a mut Self::Source) -> &'a mut Self::Target;
48
49    /// Modifies the target of the lens by applying a function to the current value.
50    fn mutate_with_fn(&self, source: &mut Self::Source, f: &dyn Fn(&Self::Target) -> Self::Target) {
51        let target = f(self.get_ref(source));
52        self.mutate(source, target);
53    }
54
55    /// Modifies the target of the lens by applying a function to the current value. This consumes the source.
56    fn modify(
57        &self,
58        source: Self::Source,
59        f: &dyn Fn(&Self::Target) -> Self::Target,
60    ) -> Self::Source {
61        let mut mutable_source = source;
62        self.mutate_with_fn(&mut mutable_source, f);
63        mutable_source
64    }
65}
66
67/// A lens that allows the target to be accessed only by cloning or copying the target value.
68pub trait ValueLens: Lens {
69    /// Gets a copy of the lens target. (This does not consume the source.)
70    fn get(&self, source: &Self::Source) -> Self::Target;
71}
72
73/// Modifies the target of the lens by applying a function to the current value.
74/// (This lives outside the `Lens` trait to allow lenses to be object-safe but
75/// still allow for static dispatch on the given closure.)
76#[doc(hidden)]
77pub fn mutate_with_fn<L: RefLens, F>(lens: &L, source: &mut L::Source, f: F)
78where
79    F: Fn(&L::Target) -> L::Target,
80{
81    let target = f(lens.get_ref(source));
82    lens.mutate(source, target);
83}
84
85/// Modifies the target of the lens by applying a function to the current value. This consumes the source.
86/// (This lives outside the `Lens` trait to allow lenses to be object-safe but
87/// still allow for static dispatch on the given closure.)
88pub fn modify<L: RefLens, F>(lens: &L, source: L::Source, f: F) -> L::Source
89where
90    F: Fn(&L::Target) -> L::Target,
91{
92    let mut mutable_source = source;
93    mutate_with_fn(lens, &mut mutable_source, f);
94    mutable_source
95}
96
97impl<L: Lens + ?Sized> Lens for Box<L> {
98    type Source = L::Source;
99    type Target = L::Target;
100
101    #[inline(always)]
102    fn path(&self) -> LensPath {
103        (**self).path()
104    }
105
106    #[inline(always)]
107    fn mutate(&self, source: &mut L::Source, target: L::Target) {
108        (**self).mutate(source, target)
109    }
110}
111
112impl<L: RefLens + ?Sized> RefLens for Box<L> {
113    #[inline(always)]
114    fn get_ref<'a>(&self, source: &'a L::Source) -> &'a L::Target {
115        (**self).get_ref(source)
116    }
117
118    #[inline(always)]
119    fn get_mut_ref<'a>(&self, source: &'a mut L::Source) -> &'a mut L::Target {
120        (**self).get_mut_ref(source)
121    }
122}
123
124impl<L: ValueLens + ?Sized> ValueLens for Box<L> {
125    #[inline(always)]
126    fn get(&self, source: &L::Source) -> L::Target {
127        (**self).get(source)
128    }
129}
130
131/// Returns a `Lens` over a single element at the given `index` for a `Vec<T>`.
132pub const fn vec_lens<T>(index: usize) -> VecLens<T> {
133    VecLens {
134        index,
135        _marker: PhantomData,
136    }
137}
138
139#[doc(hidden)]
140pub const fn vec_lens_from_marker<T>(_: PhantomData<T>, index: usize) -> VecLens<T> {
141    vec_lens(index)
142}
143
144/// A lens over a single element within a `Vec<T>`.
145pub struct VecLens<T> {
146    index: usize,
147    _marker: PhantomData<T>,
148}
149
150impl<T> VecLens<T> {
151    fn missing_index_message(index: usize) -> String {
152        format!("vector lens index {index} is out of bounds")
153    }
154}
155
156impl<T> Lens for VecLens<T> {
157    type Source = Vec<T>;
158    type Target = T;
159
160    #[inline(always)]
161    fn path(&self) -> LensPath {
162        LensPath::from_index(self.index)
163    }
164
165    #[inline(always)]
166    fn mutate(&self, source: &mut Vec<T>, target: T) {
167        let slot = source
168            .get_mut(self.index)
169            .unwrap_or_else(|| panic!("{}", Self::missing_index_message(self.index)));
170        *slot = target;
171    }
172}
173
174impl<T> RefLens for VecLens<T> {
175    #[inline(always)]
176    fn get_ref<'a>(&self, source: &'a Vec<T>) -> &'a T {
177        source
178            .get(self.index)
179            .unwrap_or_else(|| panic!("{}", Self::missing_index_message(self.index)))
180    }
181
182    #[inline(always)]
183    fn get_mut_ref<'a>(&self, source: &'a mut Vec<T>) -> &'a mut T {
184        source
185            .get_mut(self.index)
186            .unwrap_or_else(|| panic!("{}", Self::missing_index_message(self.index)))
187    }
188}
189
190/// Composes a `Lens<A, B>` with another `Lens<B, C>` to produce a new `Lens<A, C>`.
191pub fn compose<LHS, RHS>(lhs: LHS, rhs: RHS) -> ComposedLens<LHS, RHS>
192where
193    LHS: RefLens,
194    LHS::Target: 'static,
195    RHS: Lens<Source = LHS::Target>,
196{
197    ComposedLens { lhs, rhs }
198}
199
200/// Composes two `Lens`es.
201///
202/// In pseudocode:
203/// ```text,no_run
204/// compose(Lens<A, B>, Lens<B, C>) -> Lens<A, C>
205/// ```
206pub struct ComposedLens<LHS, RHS> {
207    lhs: LHS,
208    rhs: RHS,
209}
210
211impl<LHS, RHS> Lens for ComposedLens<LHS, RHS>
212where
213    LHS: RefLens,
214    LHS::Target: 'static,
215    RHS: Lens<Source = LHS::Target>,
216{
217    type Source = LHS::Source;
218    type Target = RHS::Target;
219
220    #[inline(always)]
221    fn path(&self) -> LensPath {
222        LensPath::concat(self.lhs.path(), self.rhs.path())
223    }
224
225    #[inline(always)]
226    fn mutate(&self, source: &mut LHS::Source, target: RHS::Target) {
227        let rhs_source = self.lhs.get_mut_ref(source);
228        self.rhs.mutate(rhs_source, target)
229    }
230}
231
232impl<LHS, RHS> RefLens for ComposedLens<LHS, RHS>
233where
234    LHS: RefLens,
235    LHS::Target: 'static,
236    RHS: RefLens<Source = LHS::Target>,
237{
238    #[inline(always)]
239    fn get_ref<'a>(&self, source: &'a LHS::Source) -> &'a RHS::Target {
240        self.rhs.get_ref(self.lhs.get_ref(source))
241    }
242
243    #[inline(always)]
244    fn get_mut_ref<'a>(&self, source: &'a mut LHS::Source) -> &'a mut RHS::Target {
245        self.rhs.get_mut_ref(self.lhs.get_mut_ref(source))
246    }
247}
248
249impl<LHS, RHS> ValueLens for ComposedLens<LHS, RHS>
250where
251    LHS: RefLens,
252    LHS::Target: 'static,
253    RHS: ValueLens<Source = LHS::Target>,
254{
255    #[inline(always)]
256    fn get(&self, source: &LHS::Source) -> RHS::Target {
257        self.rhs.get(self.lhs.get_ref(source))
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use crate::{Lens, LensPath, Lenses, RefLens, ValueLens, compose_lens, modify, vec_lens};
264
265    #[derive(Clone, Debug, PartialEq, Lenses)]
266    struct Struct1 {
267        int32: i32,
268        int16: i16,
269    }
270
271    #[derive(Clone, Debug, PartialEq, Lenses)]
272    struct Struct2 {
273        int32: i32,
274        string: String,
275        struct1: Struct1,
276    }
277
278    #[derive(Clone, Debug, PartialEq, Lenses)]
279    struct Struct3 {
280        int32: i32,
281        struct2: Struct2,
282    }
283
284    #[derive(Clone, Debug, PartialEq, Lenses)]
285    struct Struct4 {
286        inner_vec: Vec<Struct1>,
287    }
288
289    fn make_struct3() -> Struct3 {
290        Struct3 {
291            int32: 332,
292            struct2: Struct2 {
293                int32: 232,
294                string: "hi".to_string(),
295                struct1: Struct1 {
296                    int32: 132,
297                    int16: 116,
298                },
299            },
300        }
301    }
302
303    #[test]
304    fn a_basic_lens_should_work() {
305        let lens = crate::lens!(Struct3.struct2.struct1.int32);
306
307        let s3_0 = make_struct3();
308        assert_eq!(*lens.get_ref(&s3_0), 132);
309        assert_eq!(lens.path(), LensPath::from_vec(vec![1, 2, 0]));
310
311        let s3_1 = lens.set(s3_0, 133);
312        assert_eq!(s3_1.struct2.struct1.int32, 133);
313        assert_eq!(s3_1.struct2.struct1.int16, 116);
314
315        let s3_2 = lens.modify(s3_1, &|a| a + 1);
316        assert_eq!(s3_2.struct2.struct1.int32, 134);
317
318        let s3_3 = modify(&lens, s3_2, |a| a + 1);
319        assert_eq!(s3_3.struct2.struct1.int32, 135);
320    }
321
322    #[test]
323    fn lens_composition_should_work_with_boxed_lenses() {
324        let struct1_int32_lens: Box<dyn RefLens<Source = Struct1, Target = i32>> =
325            Box::new(Struct1Int32Lens);
326        let lens = compose_lens!(
327            Struct3Struct2Lens,
328            Box::new(Struct2Struct1Lens),
329            struct1_int32_lens
330        );
331
332        let s3_0 = make_struct3();
333        assert_eq!(*lens.get_ref(&s3_0), 132);
334
335        let s3_1 = lens.set(s3_0, 133);
336        assert_eq!(s3_1.struct2.struct1.int32, 133);
337
338        let s3_2 = lens.modify(s3_1, &|a| a + 1);
339        assert_eq!(s3_2.struct2.struct1.int32, 134);
340
341        let s3_3 = modify(&lens, s3_2, |a| a + 1);
342        assert_eq!(s3_3.struct2.struct1.int32, 135);
343    }
344
345    #[test]
346    fn value_lenses_are_generated_for_scalars_and_strings() {
347        assert_eq!(Struct1Int32Lens.get(&Struct1 { int32: 7, int16: 9 }), 7);
348        assert_eq!(
349            Struct2StringLens.get(&Struct2 {
350                int32: 0,
351                string: "hello".to_string(),
352                struct1: Struct1 { int32: 0, int16: 0 },
353            }),
354            "hello".to_string()
355        );
356    }
357
358    #[test]
359    fn a_vec_lens_should_work() {
360        let lens = vec_lens::<u32>(1);
361
362        let v0 = vec![0u32, 1, 2];
363        assert_eq!(*lens.get_ref(&v0), 1);
364        assert_eq!(lens.path(), LensPath::from_index(1));
365
366        let v1 = lens.set(v0, 42);
367        assert_eq!(v1, vec![0u32, 42, 2]);
368
369        let v2 = modify(&lens, v1, |a| a - 1);
370        assert_eq!(v2, vec![0u32, 41, 2]);
371    }
372
373    #[test]
374    #[should_panic(expected = "vector lens index 3 is out of bounds")]
375    fn a_vec_lens_panics_with_a_clear_message() {
376        let lens = vec_lens::<u32>(3);
377        let _ = lens.get_ref(&vec![0u32, 1, 2]);
378    }
379
380    #[test]
381    fn the_lens_macro_should_support_vec_indexing() {
382        let lens = crate::lens!(Struct4.inner_vec[1].int32);
383
384        let s0 = Struct4 {
385            inner_vec: vec![
386                Struct1 {
387                    int32: 42,
388                    int16: 73,
389                },
390                Struct1 {
391                    int32: 110,
392                    int16: 210,
393                },
394            ],
395        };
396        assert_eq!(*lens.get_ref(&s0), 110);
397        assert_eq!(lens.path(), LensPath::from_vec(vec![0, 1, 0]));
398
399        let s1 = lens.set(s0, 111);
400        assert_eq!(s1.inner_vec[1].int32, 111);
401
402        let s2 = modify(&lens, s1, |a| a + 1);
403        assert_eq!(s2.inner_vec[1].int32, 112);
404    }
405}