├── .github └── workflows │ └── build_and_test.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── lens-derive ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── lens-macros ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── src ├── lens.rs ├── lib.rs ├── macros.rs └── path.rs └── tests └── lib.rs /.github/workflows/build_and_test.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # Run this on pushes to `master`, or when a pull request is opened against `master` 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | test: 14 | name: Test on Rust ${{ matrix.rust }} 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | rust: 20 | - stable 21 | - beta 22 | - nightly 23 | - 1.38.0 # MSRV 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v1 28 | 29 | - name: Install Rust Toolchain 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | profile: minimal 33 | toolchain: ${{ matrix.rust }} 34 | override: true 35 | components: rustfmt, clippy 36 | 37 | - name: Build 38 | uses: actions-rs/cargo@v1 39 | with: 40 | command: build 41 | 42 | - name: Check Format 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: fmt 46 | args: --all -- --check 47 | 48 | - name: Lint 49 | uses: actions-rs/cargo@v1 50 | with: 51 | command: clippy 52 | args: --all --all-targets --all-features -- -D warnings 53 | 54 | - name: Test 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: test 58 | args: --all 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | Cargo.lock 5 | *.racertmp 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pl-lens" 3 | version = "1.0.1" 4 | edition = "2018" 5 | authors = ["Chris Campbell "] 6 | license = "MIT" 7 | description = "Provides support for lenses, which are a mechanism in functional programming for focusing on a part of a complex data structure." 8 | keywords = ["plausible", "lens", "functional"] 9 | homepage = "https://github.com/plausiblelabs/lens-rs" 10 | repository = "https://github.com/plausiblelabs/lens-rs" 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | pl-lens-derive = { path = "lens-derive", version = "1.0.1" } 15 | pl-lens-macros = { path = "lens-macros", version = "1.0.1" } 16 | proc-macro-hack = "0.5" 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2019 Plausible Labs Cooperative, Inc. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, 5 | to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pl-lens 2 | 3 | [![Build Status][gh-actions-badge]][gh-actions-url] 4 | [![Crates.io][crates-badge]][crates-url] 5 | [![Docs.rs][docs-badge]][docs-url] 6 | [![MIT licensed][mit-badge]][mit-url] 7 | 8 | [gh-actions-badge]: https://github.com/plausiblelabs/lens-rs/workflows/Build/badge.svg?event=push 9 | [gh-actions-url]: https://github.com/plausiblelabs/lens-rs/actions?query=workflow%3ABuild+branch%3Amaster 10 | [crates-badge]: https://img.shields.io/crates/v/pl-lens.svg 11 | [crates-url]: https://crates.io/crates/pl-lens 12 | [docs-badge]: https://docs.rs/pl-lens/badge.svg 13 | [docs-url]: https://docs.rs/pl-lens 14 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 15 | [mit-url]: LICENSE 16 | 17 | This Rust library provides support for lenses, which are a mechanism in functional programming for focusing on a part of a complex data structure. 18 | 19 | ## Usage 20 | 21 | Add a dependency to your `Cargo.toml`: 22 | 23 | ```toml 24 | [dependencies] 25 | pl-lens = "1.0" 26 | ``` 27 | 28 | Then, in your crate: 29 | 30 | ```rust 31 | // To use the `derive(Lenses)` macro 32 | use pl_lens::Lenses; 33 | 34 | // To use the `lens!` macro 35 | use pl_lens::lens; 36 | 37 | // To bring trait methods like `get_ref` and `set` into scope 38 | use pl_lens::{Lens, RefLens}; 39 | ``` 40 | 41 | ## Examples 42 | 43 | A `Lens` can be used to transform a conceptually-immutable data structure by changing only a portion of the data. Let's demonstrate with an example: 44 | 45 | ```rust 46 | #[derive(Lenses)] 47 | struct Address { 48 | street: String, 49 | city: String, 50 | postcode: String 51 | } 52 | 53 | #[derive(Lenses)] 54 | struct Person { 55 | name: String, 56 | age: u8, 57 | address: Address 58 | } 59 | 60 | let p0 = Person { 61 | name: "Pop Zeus".to_string(), 62 | age: 58, 63 | address: Address { 64 | street: "123 Needmore Rd".to_string(), 65 | city: "Dayton".to_string(), 66 | postcode: "99999".to_string() 67 | } 68 | }; 69 | assert_eq!(lens!(Person.name).get_ref(&p0), "Pop Zeus"); 70 | assert_eq!(lens!(Person.address.street).get_ref(&p0), "123 Needmore Rd"); 71 | 72 | let p1 = lens!(Person.address.street).set(p0, "666 Titus Ave".to_string()); 73 | assert_eq!(lens!(Person.name).get_ref(&p1), "Pop Zeus"); 74 | assert_eq!(lens!(Person.address.street).get_ref(&p1), "666 Titus Ave"); 75 | ``` 76 | 77 | # License 78 | 79 | `pl-lens` is distributed under an MIT license. See LICENSE for more details. 80 | -------------------------------------------------------------------------------- /lens-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pl-lens-derive" 3 | version = "1.0.1" 4 | edition = "2018" 5 | authors = ["Chris Campbell "] 6 | license = "MIT" 7 | description = "Provides procedural `derive` macros that are used in conjuction with the `pl-lens` crate." 8 | keywords = ["plausible", "lens", "functional"] 9 | homepage = "https://github.com/plausiblelabs/lens-rs" 10 | repository = "https://github.com/plausiblelabs/lens-rs" 11 | readme = "README.md" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0" 18 | quote = "1.0" 19 | syn = "1.0" 20 | -------------------------------------------------------------------------------- /lens-derive/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2019 Plausible Labs Cooperative, Inc. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, 5 | to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lens-derive/README.md: -------------------------------------------------------------------------------- 1 | # pl-lens-macros 2 | 3 | Provides the custom `Lenses` derive macro that is used in conjunction with 4 | the `pl-lens` crate. 5 | 6 | This crate is automatically re-exported by the `pl-lens` crate, so you generally 7 | don't need to work with it directly. 8 | 9 | # License 10 | 11 | `pl-lens-derive` is distributed under an MIT license. See LICENSE for more details. 12 | -------------------------------------------------------------------------------- /lens-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extern crate proc_macro; 7 | 8 | use proc_macro::TokenStream; 9 | use quote::{format_ident, quote}; 10 | use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Visibility}; 11 | 12 | /// Handles the `#derive(Lenses)` applied to a struct by generating a `Lens` implementation for 13 | /// each field in the struct. 14 | #[proc_macro_derive(Lenses)] 15 | pub fn lenses_derive(input: TokenStream) -> TokenStream { 16 | // Parse the input tokens into a syntax tree 17 | let input = parse_macro_input!(input as DeriveInput); 18 | 19 | // Check that the input type is a struct 20 | let data_struct: DataStruct; 21 | if let Data::Struct(s) = input.data { 22 | data_struct = s 23 | } else { 24 | panic!("`#[derive(Lenses)]` may only be applied to structs") 25 | } 26 | 27 | // Check that the struct has named fields, since that's the only 28 | // type we support at the moment 29 | let fields: Fields; 30 | if let Fields::Named(_) = data_struct.fields { 31 | fields = data_struct.fields 32 | } else { 33 | panic!("`#[derive(Lenses)]` may only be applied to structs with named fields") 34 | } 35 | 36 | // Extract the struct name 37 | let struct_name = &input.ident; 38 | 39 | // Determine the visibility of the lens struct 40 | let lens_visibility = match input.vis { 41 | Visibility::Public(..) => quote!(pub), 42 | // TODO: Handle `Crate` and `Restricted` visibliity 43 | Visibility::Crate(..) => quote!(), 44 | Visibility::Restricted(..) => quote!(), 45 | Visibility::Inherited => quote!(), 46 | }; 47 | 48 | // Generate lenses for each field in the struct 49 | let lens_items = fields.iter().enumerate().map(|(index, field)| { 50 | if let Some(field_name) = &field.ident { 51 | let field_index = index as u64; 52 | let field_type = &field.ty; 53 | 54 | // Build the Lens name from the struct name and field name (for example, "StructFieldLens") 55 | let lens_name = format_ident!( 56 | "{}{}Lens", 57 | struct_name.to_string(), 58 | to_camel_case(&field_name.to_string()) 59 | ); 60 | 61 | // Build a `ValueLens` impl if the target is a primitive 62 | // TODO: Should do this automatically for any target type that implements `Clone` 63 | let value_lens = if is_primitive(&field.ty) { 64 | quote!( 65 | #[allow(dead_code)] 66 | impl pl_lens::ValueLens for #lens_name { 67 | #[inline(always)] 68 | fn get(&self, source: &#struct_name) -> #field_type { 69 | (*source).#field_name.clone() 70 | } 71 | } 72 | ) 73 | } else { 74 | quote!() 75 | }; 76 | 77 | quote!( 78 | // Include the lens struct declaration 79 | #[allow(dead_code)] 80 | #[doc(hidden)] 81 | #lens_visibility struct #lens_name; 82 | 83 | // Include the `Lens` impl 84 | #[allow(dead_code)] 85 | impl pl_lens::Lens for #lens_name { 86 | type Source = #struct_name; 87 | type Target = #field_type; 88 | 89 | #[inline(always)] 90 | fn path(&self) -> pl_lens::LensPath { 91 | pl_lens::LensPath::new(#field_index) 92 | } 93 | 94 | #[inline(always)] 95 | fn mutate<'a>(&self, source: &'a mut #struct_name, target: #field_type) { 96 | source.#field_name = target 97 | } 98 | } 99 | 100 | // Include the `RefLens` impl 101 | #[allow(dead_code)] 102 | impl pl_lens::RefLens for #lens_name { 103 | #[inline(always)] 104 | fn get_ref<'a>(&self, source: &'a #struct_name) -> &'a #field_type { 105 | &(*source).#field_name 106 | } 107 | 108 | #[inline(always)] 109 | fn get_mut_ref<'a>(&self, source: &'a mut #struct_name) -> &'a mut #field_type { 110 | &mut (*source).#field_name 111 | } 112 | } 113 | 114 | // Include the `ValueLens` impl (only if it should be defined) 115 | #value_lens 116 | ) 117 | } else { 118 | // This should be unreachable, since we already verified above that the struct 119 | // only contains named fields 120 | panic!("`#[derive(Lenses)]` may only be applied to structs with named fields") 121 | } 122 | }); 123 | 124 | // Build a `Lenses` struct that enumerates the available lenses 125 | // for each field in the struct, for example: 126 | // struct Struct2Lenses { 127 | // int32: Struct2Int32Lens, 128 | // struct1: Struct2Struct1Lens, 129 | // struct1_lenses: Struct1Lenses 130 | // } 131 | let lenses_struct_name = format_ident!("{}Lenses", struct_name); 132 | let lenses_struct_fields = fields.iter().map(|field| { 133 | if let Some(field_name) = &field.ident { 134 | let field_lens_name = format_ident!( 135 | "{}{}Lens", 136 | struct_name, 137 | to_camel_case(&field_name.to_string()) 138 | ); 139 | if is_primitive(&field.ty) { 140 | quote!(#field_name: #field_lens_name) 141 | } else { 142 | let field_parent_lenses_field_name = format_ident!("{}_lenses", field_name); 143 | let field_parent_lenses_type_name = 144 | format_ident!("{}Lenses", to_camel_case(&field_name.to_string())); 145 | quote!( 146 | #field_name: #field_lens_name, 147 | #field_parent_lenses_field_name: #field_parent_lenses_type_name 148 | ) 149 | } 150 | } else { 151 | // This should be unreachable, since we already verified above that the struct 152 | // only contains named fields 153 | panic!("`#[derive(Lenses)]` may only be applied to structs with named fields") 154 | } 155 | }); 156 | let lenses_struct = quote!( 157 | #[allow(dead_code)] 158 | #[doc(hidden)] 159 | #lens_visibility struct #lenses_struct_name { 160 | #(#lenses_struct_fields),* 161 | } 162 | ); 163 | 164 | // Declare a `_Lenses` instance that holds the available lenses 165 | // for each field in the struct, for example: 166 | // const _Struct2Lenses: Struct2Lenses = Struct2Lenses { 167 | // int32: Struct2Int32Lens, 168 | // struct1: Struct2Struct1Lens, 169 | // struct1_lenses: _Struct1Lenses 170 | // }; 171 | let lenses_const_name = format_ident!("_{}Lenses", struct_name); 172 | let lenses_const_fields = fields.iter().map(|field| 173 | // TODO: Most of this is nearly identical to how the "Lenses" struct is declared, 174 | // except for the underscore prefix in a couple places; might be good to consolidate 175 | if let Some(field_name) = &field.ident { 176 | let field_lens_name = format_ident!("{}{}Lens", struct_name, to_camel_case(&field_name.to_string())); 177 | if is_primitive(&field.ty) { 178 | quote!(#field_name: #field_lens_name) 179 | } else { 180 | let field_parent_lenses_field_name = format_ident!("{}_lenses", field_name); 181 | let field_parent_lenses_type_name = format_ident!("_{}Lenses", to_camel_case(&field_name.to_string())); 182 | quote!( 183 | #field_name: #field_lens_name, 184 | #field_parent_lenses_field_name: #field_parent_lenses_type_name 185 | ) 186 | } 187 | } else { 188 | // This should be unreachable, since we already verified above that the struct 189 | // only contains named fields 190 | panic!("`#[derive(Lenses)]` may only be applied to structs with named fields") 191 | } 192 | ); 193 | let lenses_const = quote!( 194 | #[allow(dead_code)] 195 | #[allow(non_upper_case_globals)] 196 | #[doc(hidden)] 197 | #lens_visibility const #lenses_const_name: #lenses_struct_name = #lenses_struct_name { 198 | #(#lenses_const_fields),* 199 | }; 200 | ); 201 | 202 | // Build the output 203 | let expanded = quote! { 204 | #(#lens_items)* 205 | 206 | #lenses_struct 207 | 208 | #lenses_const 209 | }; 210 | 211 | // Hand the output tokens back to the compiler 212 | TokenStream::from(expanded) 213 | } 214 | 215 | /// Return true if the given type should be considered a primitive, i.e., whether 216 | /// it doesn't have lenses defined for it. 217 | fn is_primitive(ty: &syn::Type) -> bool { 218 | let type_str = quote!(#ty).to_string(); 219 | match type_str.as_ref() { 220 | // XXX: This is quick and dirty; we need a more reliable way to 221 | // know whether the field is a struct type for which there are 222 | // lenses derived 223 | "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "f32" | "f64" | "String" => { 224 | true 225 | } 226 | _ => false, 227 | } 228 | } 229 | 230 | // XXX: Lifted from librustc_lint/builtin.rs 231 | fn to_camel_case(s: &str) -> String { 232 | s.split('_') 233 | .flat_map(|word| { 234 | word.chars().enumerate().map(|(i, c)| { 235 | if i == 0 { 236 | c.to_uppercase().collect::() 237 | } else { 238 | c.to_lowercase().collect() 239 | } 240 | }) 241 | }) 242 | .collect::>() 243 | .concat() 244 | } 245 | 246 | #[cfg(test)] 247 | mod tests { 248 | use super::*; 249 | 250 | #[test] 251 | fn to_camel_case_should_work() { 252 | assert_eq!(to_camel_case("this_is_snake_case"), "ThisIsSnakeCase"); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /lens-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pl-lens-macros" 3 | version = "1.0.1" 4 | edition = "2018" 5 | authors = ["Chris Campbell "] 6 | license = "MIT" 7 | description = "Temporary crate used to implement the `lens!` procedural macro." 8 | keywords = ["plausible", "lens", "functional"] 9 | homepage = "https://github.com/plausiblelabs/lens-rs" 10 | repository = "https://github.com/plausiblelabs/lens-rs" 11 | readme = "README.md" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0" 18 | proc-macro-hack = "0.5" 19 | quote = "1.0" 20 | syn = { version = "1.0", features = ["full", "extra-traits"] } 21 | -------------------------------------------------------------------------------- /lens-macros/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2019 Plausible Labs Cooperative, Inc. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, 5 | to any person obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lens-macros/README.md: -------------------------------------------------------------------------------- 1 | # pl-lens-macros 2 | 3 | This is a temporary crate used to implement the `lens!` procedural macro, which requires 4 | the `proc-macro-hack` crate until such a time that procedural macros are officially 5 | supported in expression position. 6 | 7 | This crate is automatically re-exported by the `pl-lens` crate, so you generally 8 | don't need to work with it directly. 9 | 10 | # License 11 | 12 | `pl-lens-macros` is distributed under an MIT license. See LICENSE for more details. 13 | -------------------------------------------------------------------------------- /lens-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extern crate proc_macro; 7 | 8 | use proc_macro::TokenStream; 9 | use proc_macro2::TokenStream as TokenStream2; 10 | use proc_macro_hack::proc_macro_hack; 11 | use quote::{format_ident, quote}; 12 | use syn::spanned::Spanned; 13 | use syn::{parse_macro_input, Expr, ExprField, Member}; 14 | 15 | #[proc_macro_hack] 16 | pub fn lens(input: TokenStream) -> TokenStream { 17 | // Parse the input tokens into a syntax tree 18 | let expr = parse_macro_input!(input as Expr); 19 | 20 | // Check that the expression is a "named struct field access" 21 | let lens_parts: Vec; 22 | if let Expr::Field(field_access) = &expr { 23 | // Extract the list of lens names 24 | match extract_lens_parts(&field_access) { 25 | Ok(parts) => { 26 | lens_parts = parts; 27 | } 28 | Err(error) => { 29 | return error.to_compile_error().into(); 30 | } 31 | } 32 | } else { 33 | return syn::Error::new(expr.span(), "lens!() expression must be structured like a field access, e.g. `Struct.outer_field.inner_field`").to_compile_error().into(); 34 | } 35 | 36 | // At this point we should have at least two parts: the root struct name 37 | // and the first field name 38 | if lens_parts.len() < 2 { 39 | return syn::Error::new(expr.span(), "lens!() expression must be structured like a field access, e.g. `Struct.outer_field.inner_field`").to_compile_error().into(); 40 | } 41 | 42 | // We can build up the composed lens by concatenating the parts of the 43 | // expression (inserting `Lenses` or `_lenses` as needed); this relies 44 | // on the fact that the `#derive(Lenses)` macro creates a special 45 | // `struct FooLenses` for each source struct that enumerates the 46 | // lens type name for each field. 47 | // 48 | // For example, suppose we have the following lens expression: 49 | // lens!(Struct3.struct2.struct1.int32) 50 | // 51 | // We extracted the parts into `lens_parts` above, producing: 52 | // ["Struct3", "struct2", "struct1", "int32"] 53 | // 54 | // Now we can access the lenses and compose them together: 55 | // compose_lens!( 56 | // _Struct3Lenses.struct2, 57 | // _Struct3Lenses.struct2_lenses.struct1, 58 | // _Struct3Lenses.struct2_lenses.struct1_lenses.int32 59 | // ) 60 | let mut parent_lenses_name = format_ident!("_{}Lenses", lens_parts[0]); 61 | let mut child_field_name = format_ident!("{}", lens_parts[1]); 62 | let mut base_lens_expr = quote!(#parent_lenses_name); 63 | let mut lens_expr = quote!(#base_lens_expr.#child_field_name); 64 | let mut lens_exprs: Vec = vec![lens_expr.clone()]; 65 | 66 | for lens_part in lens_parts.iter().skip(2) { 67 | let prev_base_lens_expr = base_lens_expr.clone(); 68 | let prev_child_field_name = child_field_name.clone(); 69 | parent_lenses_name = format_ident!("{}_lenses", prev_child_field_name); 70 | child_field_name = format_ident!("{}", lens_part); 71 | base_lens_expr = quote!(#prev_base_lens_expr.#parent_lenses_name); 72 | lens_expr = quote!(#base_lens_expr.#child_field_name); 73 | lens_exprs.push(lens_expr.clone()); 74 | } 75 | 76 | // Build the output 77 | let expanded = quote! { 78 | pl_lens::compose_lens!(#(#lens_exprs),*); 79 | }; 80 | 81 | // Hand the output tokens back to the compiler 82 | TokenStream::from(expanded) 83 | } 84 | 85 | /// Given an expression like `Struct1.struct2_field.struct3_field`, recurse until we hit the root 86 | /// struct and then build a list of lens names that can be passed to `compose_lens!`. 87 | /// For example, the above expression would result in the following list of identifiers: 88 | /// ```text,no_run 89 | /// [Struct1, struct2_field, struct3_field] 90 | /// ``` 91 | fn extract_lens_parts(field_access: &ExprField) -> Result, syn::Error> { 92 | // Look at the parent to determine if we're at the root, or if this is a chained field access 93 | let base_parts = match &*field_access.base { 94 | Expr::Path(base_expr_path) => { 95 | // We hit the root of the expression; extract the struct name 96 | let path_segments = &base_expr_path.path.segments; 97 | if path_segments.len() > 1 { 98 | Err(syn::Error::new(field_access.span(), "lens!() expression must start with unqualified struct name, e.g. `Struct.outer_field.inner_field`")) 99 | } else { 100 | let struct_name = path_segments[0].ident.to_string(); 101 | Ok(vec![struct_name]) 102 | } 103 | } 104 | Expr::Field(base_field_access) => { 105 | // This is another field access; extract the base portion first 106 | extract_lens_parts(&base_field_access) 107 | } 108 | _ => { 109 | Err(syn::Error::new(field_access.span(), "lens!() expression must be structured like a field access, e.g. `Struct.outer_field.inner_field`")) 110 | } 111 | }; 112 | 113 | // Append the field name 114 | base_parts.and_then(|parts| { 115 | if let Member::Named(field_ident) = &field_access.member { 116 | let mut new_parts = parts; 117 | new_parts.push(field_ident.to_string()); 118 | Ok(new_parts) 119 | } else { 120 | Err(syn::Error::new( 121 | field_access.span(), 122 | "lens!() only works with named fields, e.g. `Struct.outer_field.inner_field`", 123 | )) 124 | } 125 | }) 126 | } 127 | -------------------------------------------------------------------------------- /src/lens.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | // use std::marker::PhantomData; 7 | 8 | use crate::path::LensPath; 9 | 10 | /// A lens offers a purely functional means to access and/or modify a field that is 11 | /// nested in an immutable data structure. 12 | pub trait Lens { 13 | /// The lens source type, i.e., the object containing the field. 14 | type Source; 15 | 16 | /// The lens target type, i.e., the field to be accessed or modified. 17 | type Target; 18 | 19 | /// Returns a `LensPath` that describes the target of this lens relative to its source. 20 | fn path(&self) -> LensPath; 21 | 22 | /// Sets the target of the lens. (This requires a mutable source reference, and as such is typically 23 | /// only used internally.) 24 | #[doc(hidden)] 25 | fn mutate<'a>(&self, source: &'a mut Self::Source, target: Self::Target); 26 | 27 | /// Sets the target of the lens and returns the new state of the source. (This consumes the source.) 28 | fn set(&self, source: Self::Source, target: Self::Target) -> Self::Source { 29 | let mut mutable_source = source; 30 | { 31 | self.mutate(&mut mutable_source, target); 32 | } 33 | mutable_source 34 | } 35 | } 36 | 37 | /// A lens that allows the target to be accessed and mutated by reference. 38 | pub trait RefLens: Lens { 39 | /// Gets a reference to the target of the lens. (This does not consume the source.) 40 | fn get_ref<'a>(&self, source: &'a Self::Source) -> &'a Self::Target; 41 | 42 | /// Gets a mutable reference to the target of the lens. (This requires a mutable source reference, 43 | /// and as such is typically only used internally.) 44 | #[doc(hidden)] 45 | fn get_mut_ref<'a>(&self, source: &'a mut Self::Source) -> &'a mut Self::Target; 46 | 47 | /// Modifies the target of the lens by applying a function to the current value. 48 | fn mutate_with_fn<'a>( 49 | &self, 50 | source: &'a mut Self::Source, 51 | f: &dyn Fn(&Self::Target) -> Self::Target, 52 | ) { 53 | let target = f(self.get_ref(source)); 54 | self.mutate(source, target); 55 | } 56 | 57 | /// Modifies the target of the lens by applying a function to the current value. This consumes the source. 58 | fn modify( 59 | &self, 60 | source: Self::Source, 61 | f: &dyn Fn(&Self::Target) -> Self::Target, 62 | ) -> Self::Source { 63 | let mut mutable_source = source; 64 | { 65 | self.mutate_with_fn(&mut mutable_source, f); 66 | } 67 | mutable_source 68 | } 69 | } 70 | 71 | /// A lens that allows the target to be accessed only by cloning or copying the target value. 72 | pub trait ValueLens: Lens { 73 | /// Gets a copy of the lens target. (This does not consume the source.) 74 | fn get(&self, source: &Self::Source) -> Self::Target; 75 | } 76 | 77 | /// Modifies the target of the lens by applying a function to the current value. 78 | /// (This lives outside the `Lens` trait to allow lenses to be object-safe but 79 | /// still allow for static dispatch on the given closure.) 80 | #[doc(hidden)] 81 | pub fn mutate_with_fn<'a, L: RefLens, F>(lens: &L, source: &'a mut L::Source, f: F) 82 | where 83 | F: Fn(&L::Target) -> L::Target, 84 | { 85 | let target = f(lens.get_ref(source)); 86 | lens.mutate(source, target); 87 | } 88 | 89 | /// Modifies the target of the lens by applying a function to the current value. This consumes the source. 90 | /// (This lives outside the `Lens` trait to allow lenses to be object-safe but 91 | /// still allow for static dispatch on the given closure.) 92 | pub fn modify(lens: &L, source: L::Source, f: F) -> L::Source 93 | where 94 | F: Fn(&L::Target) -> L::Target, 95 | { 96 | let mut mutable_source = source; 97 | { 98 | mutate_with_fn(lens, &mut mutable_source, f); 99 | } 100 | mutable_source 101 | } 102 | 103 | // Automatically provides implementation of `Lens` trait for all `Box`. 104 | impl Lens for Box { 105 | type Source = L::Source; 106 | type Target = L::Target; 107 | 108 | #[inline(always)] 109 | fn path(&self) -> LensPath { 110 | (**self).path() 111 | } 112 | 113 | #[inline(always)] 114 | fn mutate<'a>(&self, source: &'a mut L::Source, target: L::Target) { 115 | (**self).mutate(source, target) 116 | } 117 | } 118 | 119 | // Automatically provides implementation of `RefLens` trait for all `Box`. 120 | impl RefLens for Box { 121 | #[inline(always)] 122 | fn get_ref<'a>(&self, source: &'a L::Source) -> &'a L::Target { 123 | (**self).get_ref(source) 124 | } 125 | 126 | #[inline(always)] 127 | fn get_mut_ref<'a>(&self, source: &'a mut L::Source) -> &'a mut L::Target { 128 | (**self).get_mut_ref(source) 129 | } 130 | } 131 | 132 | // Automatically provides implementation of `ValueLens` trait for all `Box`. 133 | impl ValueLens for Box { 134 | #[inline(always)] 135 | fn get(&self, source: &L::Source) -> L::Target { 136 | (**self).get(source) 137 | } 138 | } 139 | 140 | // /// Returns a `Lens` over a single element at the given `index` for a `Vec`. 141 | // pub const fn vec_lens(index: usize) -> impl RefLens, Target=T> { 142 | // VecLens { index: index, _marker: PhantomData:: } 143 | // } 144 | 145 | // struct VecLens { 146 | // index: usize, 147 | // _marker: PhantomData 148 | // } 149 | 150 | // impl Lens for VecLens { 151 | // type Source = Vec; 152 | // type Target = T; 153 | 154 | // #[inline(always)] 155 | // fn path(&self) -> LensPath { 156 | // LensPath::new(self.index as u64) 157 | // } 158 | 159 | // #[inline(always)] 160 | // fn mutate<'a>(&self, source: &'a mut Vec, target: T) { 161 | // source[self.index] = target; 162 | // } 163 | // } 164 | 165 | // impl RefLens for VecLens { 166 | // #[inline(always)] 167 | // fn get_ref<'a>(&self, source: &'a Vec) -> &'a T { 168 | // source.get(self.index).unwrap() 169 | // } 170 | 171 | // #[inline(always)] 172 | // fn get_mut_ref<'a>(&self, source: &'a mut Vec) -> &'a mut T { 173 | // source.get_mut(self.index).unwrap() 174 | // } 175 | // } 176 | 177 | /// Composes a `Lens` with another `Lens` to produce a new `Lens`. 178 | // TODO: Bounds are unstable in `const fn`, so we'll do without the const-ness for now 179 | // pub const fn compose(lhs: LHS, rhs: RHS) -> ComposedLens 180 | // where LHS: RefLens, LHS::Target: 'static, RHS: Lens 181 | pub fn compose(lhs: LHS, rhs: RHS) -> ComposedLens 182 | where 183 | LHS: RefLens, 184 | LHS::Target: 'static, 185 | RHS: Lens, 186 | { 187 | ComposedLens { lhs, rhs } 188 | } 189 | 190 | /// Composes two `Lens`es. 191 | /// 192 | /// In pseudocode: 193 | /// ```text,no_run 194 | /// compose(Lens, Lens) -> Lens 195 | /// ``` 196 | pub struct ComposedLens { 197 | /// The left-hand side of the composition. 198 | lhs: LHS, 199 | 200 | /// The right-hand side of the composition. 201 | rhs: RHS, 202 | } 203 | 204 | impl Lens for ComposedLens 205 | where 206 | LHS: RefLens, 207 | LHS::Target: 'static, 208 | RHS: Lens, 209 | { 210 | type Source = LHS::Source; 211 | type Target = RHS::Target; 212 | 213 | #[inline(always)] 214 | fn path(&self) -> LensPath { 215 | LensPath::concat(self.lhs.path(), self.rhs.path()) 216 | } 217 | 218 | #[inline(always)] 219 | fn mutate<'a>(&self, source: &'a mut LHS::Source, target: RHS::Target) { 220 | let rhs_source = self.lhs.get_mut_ref(source); 221 | self.rhs.mutate(rhs_source, target) 222 | } 223 | } 224 | 225 | impl RefLens for ComposedLens 226 | where 227 | LHS: RefLens, 228 | LHS::Target: 'static, 229 | RHS: RefLens, 230 | { 231 | #[inline(always)] 232 | fn get_ref<'a>(&self, source: &'a LHS::Source) -> &'a RHS::Target { 233 | self.rhs.get_ref(self.lhs.get_ref(source)) 234 | } 235 | 236 | #[inline(always)] 237 | fn get_mut_ref<'a>(&self, source: &'a mut LHS::Source) -> &'a mut RHS::Target { 238 | self.rhs.get_mut_ref(self.lhs.get_mut_ref(source)) 239 | } 240 | } 241 | 242 | impl ValueLens for ComposedLens 243 | where 244 | LHS: RefLens, 245 | LHS::Target: 'static, 246 | RHS: ValueLens, 247 | { 248 | #[inline(always)] 249 | fn get(&self, source: &LHS::Source) -> RHS::Target { 250 | self.rhs.get(self.lhs.get_ref(source)) 251 | } 252 | } 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | use super::*; 257 | use pl_lens_derive::Lenses; 258 | 259 | #[derive(Clone, Debug, PartialEq, Lenses)] 260 | struct Struct1 { 261 | int32: i32, 262 | int16: i16, 263 | } 264 | 265 | #[derive(Clone, Debug, PartialEq, Lenses)] 266 | struct Struct2 { 267 | int32: i32, 268 | string: String, 269 | struct1: Struct1, 270 | } 271 | 272 | #[derive(Clone, Debug, PartialEq, Lenses)] 273 | struct Struct3 { 274 | int32: i32, 275 | struct2: Struct2, 276 | } 277 | 278 | // #[derive(Clone, Debug, PartialEq, Lenses)] 279 | // struct Struct4 { 280 | // inner_vec: Vec 281 | // } 282 | 283 | #[test] 284 | fn a_basic_lens_should_work() { 285 | #[allow(dead_code)] 286 | let lens = lens!(Struct3.struct2.struct1.int32); 287 | 288 | let s3_0 = Struct3 { 289 | int32: 332, 290 | struct2: Struct2 { 291 | int32: 232, 292 | string: "hi".to_string(), 293 | struct1: Struct1 { 294 | int32: 132, 295 | int16: 116, 296 | }, 297 | }, 298 | }; 299 | assert_eq!(*lens.get_ref(&s3_0), 132); 300 | 301 | let s3_1 = lens.set(s3_0, 133); 302 | assert_eq!(s3_1.struct2.struct1.int32, 133); 303 | assert_eq!(s3_1.struct2.struct1.int16, 116); 304 | 305 | let s3_2 = lens.modify(s3_1, &|a| a + 1); 306 | assert_eq!(s3_2.struct2.struct1.int32, 134); 307 | assert_eq!(s3_2.struct2.struct1.int16, 116); 308 | 309 | let s3_3 = modify(&lens, s3_2, |a| a + 1); 310 | assert_eq!(s3_3.struct2.struct1.int32, 135); 311 | assert_eq!(s3_3.struct2.struct1.int16, 116); 312 | } 313 | 314 | // #[test] 315 | // fn a_vec_lens_should_work() { 316 | // let lens = vec_lens::(1); 317 | 318 | // let v0 = vec!(0u32, 1, 2); 319 | // assert_eq!(*lens.get_ref(&v0), 1); 320 | 321 | // let v1 = lens.set(v0, 42); 322 | // assert_eq!(v1, vec!(0u32, 42, 2)); 323 | 324 | // let v2 = modify(&lens, v1, |a| a - 1); 325 | // assert_eq!(v2, vec!(0u32, 41, 2)); 326 | // } 327 | 328 | // #[test] 329 | // fn the_lens_macro_should_support_vec_indexing() { 330 | // let lens = lens!(Struct4.inner_vec[1].foo); 331 | 332 | // let s0 = Struct4 { inner_vec: vec!( 333 | // Struct1 { foo: 42, bar: 73 }, 334 | // Struct1 { foo: 110, bar: 210 } 335 | // )}; 336 | // assert_eq!(*lens.get_ref(&s0), 110); 337 | 338 | // let s1 = lens.set(s0, 111); 339 | // assert_eq!(s1.inner_vec[1].foo, 111); 340 | 341 | // let s2 = modify(&lens, s1, |a| a + 1); 342 | // assert_eq!(s2.inner_vec[1].foo, 112); 343 | // } 344 | 345 | #[test] 346 | fn lens_composition_should_work_with_boxed_lenses() { 347 | let struct1_int32_lens: Box> = 348 | Box::new(Struct1Int32Lens); 349 | let lens = compose_lens!( 350 | Struct3Struct2Lens, 351 | Box::new(Struct2Struct1Lens), 352 | struct1_int32_lens 353 | ); 354 | 355 | let s3_0 = Struct3 { 356 | int32: 332, 357 | struct2: Struct2 { 358 | int32: 232, 359 | string: "hi".to_string(), 360 | struct1: Struct1 { 361 | int32: 132, 362 | int16: 116, 363 | }, 364 | }, 365 | }; 366 | assert_eq!(*lens.get_ref(&s3_0), 132); 367 | 368 | let s3_1 = lens.set(s3_0, 133); 369 | assert_eq!(s3_1.struct2.struct1.int32, 133); 370 | assert_eq!(s3_1.struct2.struct1.int16, 116); 371 | 372 | let s3_2 = lens.modify(s3_1, &|a| a + 1); 373 | assert_eq!(s3_2.struct2.struct1.int32, 134); 374 | assert_eq!(s3_2.struct2.struct1.int16, 116); 375 | 376 | let s3_3 = modify(&lens, s3_2, |a| a + 1); 377 | assert_eq!(s3_3.struct2.struct1.int32, 135); 378 | assert_eq!(s3_3.struct2.struct1.int16, 116); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | extern crate self as pl_lens; 7 | 8 | use proc_macro_hack::proc_macro_hack; 9 | 10 | // Re-export the pl-lens-derive crate 11 | pub use pl_lens_derive::*; 12 | 13 | /// This is a macro-based shorthand that allows us to write: 14 | /// 15 | /// ```text,no_run 16 | /// lens!(SomeStruct.foo.bar_vec[3].baz) 17 | /// ``` 18 | /// 19 | /// instead of: 20 | /// 21 | /// ```text,no_run 22 | /// compose_lens!(SomeStructFooLens, FooBarVecLens, vec_lens::(3), BarThingBazLens) 23 | /// ``` 24 | #[proc_macro_hack] 25 | pub use pl_lens_macros::lens; 26 | 27 | // The following is necessary to make exported macros visible. 28 | #[macro_use] 29 | mod macros; 30 | pub use self::macros::*; 31 | 32 | mod lens; 33 | mod path; 34 | 35 | pub use self::lens::*; 36 | pub use self::path::*; 37 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | /// Provides a shorthand for composing a series of lenses. 7 | #[macro_export] 8 | macro_rules! compose_lens { 9 | { $head:expr } => { 10 | $head 11 | }; 12 | { $head:expr, $($tail:expr),+ } => { 13 | pl_lens::compose($head, pl_lens::compose_lens!($($tail),+)) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | use std::fmt; 7 | 8 | /// An element in a `LensPath`. 9 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] 10 | pub struct LensPathElement { 11 | id: u64, 12 | } 13 | 14 | impl LensPathElement { 15 | pub fn new(id: u64) -> LensPathElement { 16 | LensPathElement { id } 17 | } 18 | } 19 | 20 | /// Describes a lens relative to a source data structure. 21 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 22 | pub struct LensPath { 23 | /// The path elements. 24 | pub elements: Vec, 25 | } 26 | 27 | impl LensPath { 28 | /// Creates a new `LensPath` with no elements. 29 | pub fn empty() -> LensPath { 30 | LensPath { elements: vec![] } 31 | } 32 | 33 | /// Creates a new `LensPath` with a single element. 34 | pub fn new(id: u64) -> LensPath { 35 | LensPath { 36 | elements: vec![LensPathElement { id }], 37 | } 38 | } 39 | 40 | /// Creates a new `LensPath` with a single index (for an indexed type such as `Vec`). 41 | pub fn from_index(index: usize) -> LensPath { 42 | LensPath { 43 | elements: vec![LensPathElement { id: index as u64 }], 44 | } 45 | } 46 | 47 | /// Creates a new `LensPath` with two elements. 48 | pub fn from_pair(id0: u64, id1: u64) -> LensPath { 49 | LensPath { 50 | elements: vec![LensPathElement { id: id0 }, LensPathElement { id: id1 }], 51 | } 52 | } 53 | 54 | /// Creates a new `LensPath` from a vector of element identifiers. 55 | pub fn from_vec(ids: Vec) -> LensPath { 56 | LensPath { 57 | elements: ids.iter().map(|id| LensPathElement { id: *id }).collect(), 58 | } 59 | } 60 | 61 | /// Creates a new `LensPath` that is the concatenation of the two paths. 62 | pub fn concat(lhs: LensPath, rhs: LensPath) -> LensPath { 63 | let mut elements = lhs.elements; 64 | elements.extend(&rhs.elements); 65 | LensPath { elements } 66 | } 67 | } 68 | 69 | impl fmt::Debug for LensPath { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | write!( 72 | f, 73 | "[{}]", 74 | self.elements 75 | .iter() 76 | .map(|elem| elem.id.to_string()) 77 | .collect::>() 78 | .join(", ") 79 | ) 80 | } 81 | } 82 | 83 | #[test] 84 | fn test_lens_path_concat() { 85 | let p0 = LensPath::from_vec(vec![1, 2, 3]); 86 | let p1 = LensPath::from_vec(vec![4, 5]); 87 | let p2 = LensPath::concat(p0, p1); 88 | assert_eq!(p2, LensPath::from_vec(vec![1, 2, 3, 4, 5])); 89 | } 90 | 91 | #[test] 92 | fn test_lens_path_debug() { 93 | let path = LensPath::from_vec(vec![1, 2, 3, 4, 5]); 94 | assert_eq!(format!("{:?}", path), "[1, 2, 3, 4, 5]".to_string()); 95 | } 96 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Plausible Labs Cooperative, Inc. 3 | // All rights reserved. 4 | // 5 | 6 | use pl_lens::Lenses; 7 | 8 | #[derive(Lenses)] 9 | struct Address { 10 | street: String, 11 | city: String, 12 | postcode: String, 13 | } 14 | 15 | #[derive(Lenses)] 16 | struct Person { 17 | name: String, 18 | age: u8, 19 | address: Address, 20 | } 21 | 22 | #[test] 23 | fn a_simple_nested_data_structure_should_be_lensable() { 24 | use pl_lens::{lens, Lens, RefLens}; 25 | 26 | let p0 = Person { 27 | name: "Pop Zeus".to_string(), 28 | age: 58, 29 | address: Address { 30 | street: "123 Needmore Rd".to_string(), 31 | city: "Dayton".to_string(), 32 | postcode: "99999".to_string(), 33 | }, 34 | }; 35 | assert_eq!(lens!(Person.name).get_ref(&p0), "Pop Zeus"); 36 | assert_eq!(lens!(Person.address.street).get_ref(&p0), "123 Needmore Rd"); 37 | 38 | let p1 = lens!(Person.address.street).set(p0, "666 Titus Ave".to_string()); 39 | assert_eq!(lens!(Person.name).get_ref(&p1), "Pop Zeus"); 40 | assert_eq!(lens!(Person.address.street).get_ref(&p1), "666 Titus Ave"); 41 | } 42 | --------------------------------------------------------------------------------