├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── guide.md ├── readme.md ├── rovv ├── Cargo.toml ├── build.rs ├── readme.md └── src │ └── lib.rs ├── rovv_derive ├── Cargo.toml ├── readme.md └── src │ └── lib.rs └── rovv_test ├── Cargo.toml └── src └── lib.rs /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "rovv", 4 | "rovv_derive", 5 | "rovv_test", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Yuetao Deng 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /guide.md: -------------------------------------------------------------------------------- 1 | # rovv guide 2 | 3 | Several days ago, I implemented a row type (a poor-man's row polymorphism) for Rust "by the way". 4 | 5 | You could think of it as the syntax sugar for [`lens-rs`](https://github.com/TOETOE55/lens-rs). 6 | 7 | In `lens-rs`, you can describe a type `T` with field `.a` as the example: 8 | 9 | ```rust 10 | 11 | fn field_a(t: &T) -> &str 12 | where 13 | T: LensRef, 14 | { 15 | t.view_ref(optics!(a)) 16 | } 17 | ​ 18 | let foo = Foo { 19 | a: String::from("this is Foo"), 20 | b: 0, 21 | } 22 | ​ 23 | let bar = Bar { 24 | a: String::from("this is Bar"), 25 | c: 1, 26 | } 27 | ​ 28 | assert_eq!(field_a(&foo), "this is Foo"); 29 | assert_eq!(field_a(&bar), "this is Bar"); 30 | 31 | ``` 32 | 33 | Now you can rewrite it in `rovv`: 34 | 35 | ```rust 36 | 37 | fn field_a(r: &row! { ref a: String, ..}) -> &str { 38 | r.view_ref(optics!(a)) 39 | } 40 | ​ 41 | fn bar(s: &str) -> row! { a: &str, .. } { 42 | Foo { 43 | a: s, 44 | b: 1, 45 | } 46 | } 47 | 48 | ``` 49 | 50 | Macro `row!` has provided the following syntax: 51 | 52 | ```rust 53 | 54 | // multiple fields 55 | row! { a: A, b: B, c: C, .. } 56 | ​ 57 | // describe the mutability of field 58 | row! { 59 | a: A, // you can move `.a`,rovv has impl Lens for the row 60 | ref b: B, // can only borrow `.b` as immutable,rovv has impl LensRef for the row 61 | mut c: C, // can borrow `.c` as mutable,has impl LensMut for the row 62 | .. 63 | } 64 | ​ 65 | // describe the number of the field 66 | row! { 67 | a: A?, // has 0 or 1 `.a`,has impl Prism 68 | .. 69 | } 70 | ​ 71 | row! { 72 | a: A*, // has many `.a`,has impl Traversal 73 | .. 74 | } 75 | ​ 76 | // row outlives 'a (and 'b) 77 | row! { <'a, 'b> 78 | a: &'a A, 79 | b: &mut 'b B, 80 | .. 81 | } 82 | 83 | ``` 84 | 85 | In addition to `row!` macro, the `dyn_row!` macro has been also provided with the same syntax: 86 | 87 | ```rust 88 | 89 | fn dyn_field_a(r: &dyn_row! { ref a: String, ..}) -> &str { 90 | r.view_ref(optics!(a)) 91 | } 92 | ​ 93 | fn bar<'a>(s: &'a str) -> Box a: &'a str, .. }> { 94 | Box::new(Foo { 95 | a: s, 96 | b: 2, 97 | }) 98 | } 99 | 100 | ``` 101 | 102 | The differences are: 103 | 104 | * `row!` is the sugar of impl trait. (`impl Lens + ...`) 105 | * `dyn_row!` is the sugar of dyn trait. (`dyn Lens + ...`, some magic here) 106 | 107 | Don't forget to implement `Lens` for `Foo` and `Bar` before using `rovv`: 108 | 109 | ```rust 110 | 111 | #[derive(Copy, Clone, Debug, Optic, Lens)] 112 | struct Foo { 113 | #[optic] 114 | a: A, 115 | #[optic] 116 | b: B, 117 | } 118 | ​ 119 | #[derive(Clone, Debug, Optic, Lens)] 120 | struct Bar { 121 | #[optic] 122 | a: String, 123 | #[optic] 124 | c: i32, 125 | } 126 | 127 | ``` 128 | 129 | And don't forget add 130 | 131 | ```toml 132 | 133 | [package.metadata.inwelling] 134 | rovv = true 135 | 136 | ``` 137 | 138 | in Cargo.toml -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | `rovv` is a crate to provide anonymous row type, which fields can be access by [`lens-rs`](https://github.com/TOETOE55/lens-rs) 4 | 5 | -------------------------------------------------------------------------------- /rovv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rovv" 3 | version = "0.2.1" 4 | authors = ["Xyzt Toe <584605539@qq.com>"] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | readme = "readme.md" 8 | keywords = [ "anonymous", "structural", "polymorphism", "row"] 9 | categories = [ "rust-patterns" ] 10 | repository = "https://github.com/TOETOE55/rovv" 11 | description = "provide the anonymous row type (poor-man's row polymorphism) in rust" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [build-dependencies] 16 | inwelling = "0.3" 17 | proc-macro2 = "1.0" 18 | quote = "1.0" 19 | syn = { version = "1.0", features = ["extra-traits","full","visit","visit-mut"] } 20 | 21 | [dependencies] 22 | rovv_derive = { path = "../rovv_derive", version = "0.2" } 23 | lens-rs = "0.3" -------------------------------------------------------------------------------- /rovv/build.rs: -------------------------------------------------------------------------------- 1 | use inwelling::*; 2 | 3 | use proc_macro2::Span; 4 | use quote::*; 5 | use std::collections::HashMap; 6 | use std::{env, fs, path::PathBuf}; 7 | use syn::{ 8 | parse::{Parse, ParseStream}, 9 | punctuated::Punctuated, 10 | visit::Visit, 11 | Token, 12 | }; 13 | 14 | fn main() { 15 | let mut row_map = RowMap::new(); 16 | let mut row_collector = DynRowCollector(&mut row_map); 17 | 18 | for section in inwelling(Opts { 19 | watch_manifest: true, 20 | watch_rs_files: true, 21 | dump_rs_paths: true, 22 | }) 23 | .sections 24 | { 25 | for rs_path in section.rs_paths.unwrap() { 26 | let contents = String::from_utf8(fs::read(rs_path).unwrap()).unwrap(); 27 | if let Some(syntax) = syn::parse_file(&contents) { 28 | row_collector.visit_file(&syntax); 29 | } 30 | } 31 | } 32 | 33 | let mut output = String::new(); 34 | for (row_name, lens_bounds) in row_map { 35 | let generics = (0..lens_bounds.len()) 36 | .flat_map(|n| vec![format_ident!("K{}", n), format_ident!("V{}", n)]) 37 | .collect::>(); 38 | let bounds = lens_bounds 39 | .into_iter() 40 | .enumerate() 41 | .map(|(n, bound)| (format_ident!("K{}", n), format_ident!("V{}", n), bound)) 42 | .map(|(k, v, bound)| quote! { lens_rs::#bound<#k, #v> }) 43 | .collect::>(); 44 | let row_ident = syn::Ident::new(&row_name, Span::call_site()); 45 | 46 | output += "e! { 47 | #[allow(non_camel_case_types)] 48 | pub trait #row_ident<#generics>: #bounds { } 49 | } 50 | .to_string(); 51 | output += &"\n"; 52 | output += "e! { 53 | impl #row_ident<#generics> for T 54 | where 55 | T: #bounds, 56 | { } 57 | } 58 | .to_string(); 59 | output += &"\n"; 60 | } 61 | 62 | let out_path = PathBuf::from(env::var("OUT_DIR").expect("$OUT_DIR should exist.")); 63 | std::fs::write(out_path.join("dyn_row.rs"), output).expect("optics.rs should be generated."); 64 | } 65 | 66 | #[derive(Clone, Debug)] 67 | enum Mutability { 68 | Ref(Token![ref]), 69 | Mut(Token![mut]), 70 | Move, 71 | } 72 | 73 | #[derive(Clone, Debug)] 74 | enum TypeSuffix { 75 | Empty, 76 | Star(Token![*]), 77 | Question(Token![?]), 78 | } 79 | 80 | #[derive(Clone, Debug)] 81 | enum Key { 82 | Ident(syn::Ident), 83 | Type { 84 | _bracket_token: syn::token::Bracket, 85 | key_type: syn::Type, 86 | }, 87 | } 88 | 89 | /// ref a: A?, mut b: B*, c: C, [K]: V 90 | #[derive(Clone, Debug)] 91 | struct RowTypeField { 92 | mutability: Mutability, 93 | key: Key, 94 | _colon_token: Token![:], 95 | field_type: syn::Type, 96 | suffix: TypeSuffix, 97 | } 98 | 99 | /// row! { a: A, b: B, c: C, .. : Trait1 + Trait2 + 'a } 100 | #[derive(Clone, Debug)] 101 | struct RowType { 102 | fields: Punctuated, 103 | _dot2token: Token![..], 104 | _colon_token: Option, 105 | bounds: Punctuated, 106 | } 107 | 108 | impl Parse for Mutability { 109 | fn parse(input: ParseStream) -> syn::Result { 110 | let lookahead = input.lookahead1(); 111 | if lookahead.peek(Token![ref]) { 112 | Ok(Mutability::Ref(input.parse()?)) 113 | } else if lookahead.peek(Token![mut]) { 114 | Ok(Mutability::Mut(input.parse()?)) 115 | } else if lookahead.peek(syn::Ident) || lookahead.peek(syn::token::Bracket) { 116 | Ok(Mutability::Move) 117 | } else { 118 | Err(syn::Error::new( 119 | proc_macro2::Span::call_site(), 120 | "expected `ref`, `mut` or nothing", 121 | )) 122 | } 123 | } 124 | } 125 | 126 | impl Parse for TypeSuffix { 127 | fn parse(input: ParseStream) -> syn::Result { 128 | let lookahead = input.lookahead1(); 129 | if lookahead.peek(Token![*]) { 130 | Ok(TypeSuffix::Star(input.parse()?)) 131 | } else if lookahead.peek(Token![?]) { 132 | Ok(TypeSuffix::Question(input.parse()?)) 133 | } else { 134 | Ok(TypeSuffix::Empty) 135 | } 136 | } 137 | } 138 | 139 | impl Parse for Key { 140 | fn parse(input: ParseStream) -> syn::Result { 141 | if input.peek(syn::token::Bracket) { 142 | let content; 143 | let _bracket_token = syn::bracketed!(content in input); 144 | Ok(Self::Type { 145 | _bracket_token, 146 | key_type: content.parse()?, 147 | }) 148 | } else { 149 | Ok(Self::Ident(input.parse()?)) 150 | } 151 | } 152 | } 153 | 154 | impl ToTokens for Key { 155 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 156 | match self { 157 | Key::Ident(id) => tokens.extend(quote! { lens_rs::Optics![#id] }), 158 | Key::Type { key_type, .. } => key_type.to_tokens(tokens), 159 | } 160 | } 161 | } 162 | 163 | impl Parse for RowTypeField { 164 | fn parse(input: ParseStream) -> syn::Result { 165 | Ok(Self { 166 | mutability: input.parse()?, 167 | key: input.parse()?, 168 | _colon_token: input.parse()?, 169 | field_type: input.parse()?, 170 | suffix: input.parse()?, 171 | }) 172 | } 173 | } 174 | 175 | impl Parse for RowType { 176 | fn parse(input: ParseStream) -> syn::Result { 177 | let mut fields = Punctuated::new(); 178 | while !input.is_empty() && !input.peek(Token![..]) { 179 | let row_field = input.call(RowTypeField::parse)?; 180 | fields.push_value(row_field); 181 | if input.is_empty() { 182 | break; 183 | } 184 | let punct: Token![,] = input.parse()?; 185 | fields.push_punct(punct); 186 | } 187 | 188 | let _dot2token = if fields.empty_or_trailing() && input.peek(Token![..]) { 189 | input.parse()? 190 | } else { 191 | return Err(syn::Error::new( 192 | proc_macro2::Span::call_site(), 193 | "expected `..` token", 194 | )); 195 | }; 196 | 197 | let _colon_token = if input.peek(Token![:]) { 198 | Some(input.parse()?) 199 | } else { 200 | return Ok(Self { 201 | fields, 202 | _dot2token, 203 | _colon_token: None, 204 | bounds: Default::default(), 205 | }); 206 | }; 207 | 208 | Ok(Self { 209 | fields, 210 | _dot2token, 211 | _colon_token, 212 | bounds: Punctuated::parse_terminated(&input)?, 213 | }) 214 | } 215 | } 216 | 217 | fn join_row_field( 218 | mut fields: Vec, 219 | ) -> (String, Vec, Vec, Vec) { 220 | fields.sort_by_key(|field| map_trait(&field.suffix, &field.mutability)); 221 | 222 | let mut dyn_row_name = "_row".to_string(); 223 | let mut fields_key = Vec::new(); 224 | let mut fields_ty = Vec::new(); 225 | let mut optics_trait = Vec::new(); 226 | for field in fields { 227 | let trait_name = map_trait(&field.suffix, &field.mutability); 228 | dyn_row_name += &format!("_{}_", trait_name); 229 | optics_trait.push(syn::Ident::new(trait_name, proc_macro2::Span::call_site())); 230 | fields_ty.push(field.field_type); 231 | fields_key.push(field.key); 232 | } 233 | 234 | (dyn_row_name, fields_key, fields_ty, optics_trait) 235 | } 236 | 237 | fn map_trait(suffix: &TypeSuffix, mutability: &Mutability) -> &'static str { 238 | match (suffix, mutability) { 239 | (TypeSuffix::Empty, Mutability::Ref(_)) => "LensRef", 240 | (TypeSuffix::Empty, Mutability::Mut(_)) => "LensMut", 241 | (TypeSuffix::Empty, Mutability::Move) => "Lens", 242 | (TypeSuffix::Star(_), Mutability::Ref(_)) => "TraversalRef", 243 | (TypeSuffix::Star(_), Mutability::Mut(_)) => "TraversalMut", 244 | (TypeSuffix::Star(_), Mutability::Move) => "Traversal", 245 | (TypeSuffix::Question(_), Mutability::Ref(_)) => "PrismRef", 246 | (TypeSuffix::Question(_), Mutability::Mut(_)) => "PrismMut", 247 | (TypeSuffix::Question(_), Mutability::Move) => "Prism", 248 | } 249 | } 250 | 251 | type RowMap = HashMap>; 252 | 253 | struct DynRowCollector<'a>(&'a mut RowMap); 254 | 255 | impl<'a> DynRowCollector<'a> { 256 | fn parse_row(&mut self, input: proc_macro2::TokenStream) { 257 | let row_type = syn::parse2::(input).expect("row invalid"); 258 | let fields: Vec = row_type.fields.into_iter().collect::>(); 259 | let (row_ident, _, _, optics_trait) = join_row_field(fields); 260 | self.0.entry(row_ident).or_insert(optics_trait); 261 | } 262 | } 263 | 264 | impl<'a> Visit<'_> for DynRowCollector<'a> { 265 | fn visit_macro(&mut self, mac: &syn::Macro) { 266 | syn::visit::visit_macro(self, mac); 267 | 268 | if mac.path.leading_colon.is_none() && mac.path.segments.len() == 1 { 269 | let seg = mac.path.segments.first().unwrap(); 270 | if seg.arguments == syn::PathArguments::None && (seg.ident == "dyn_row" || seg.ident == "row") { 271 | self.parse_row(mac.tokens.clone().into()); 272 | } 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /rovv/readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | `rovv` is a crate to provide a "poor-man's" row-polymorphism for rust base on [`lens-rs`](https://crates.io/crates/lens-rs). 4 | 5 | ## What is row poly? 6 | 7 | > In programming language type theory, row polymorphism is a kind of polymorphism that allows one to write programs that are polymorphic on record field types (also known as rows, hence row polymorphism). 8 | > 9 | > -- wikipedia 10 | 11 | Considering a function in PureScript: 12 | 13 | ```purescript 14 | \r -> r.x 15 | ``` 16 | 17 | you can pass a record into the function above 18 | 19 | ```purescript 20 | { x: 1 } 21 | ``` 22 | 23 | or even 24 | 25 | ```purescript 26 | { x: 1, y: 2, z: 3 } 27 | ``` 28 | 29 | That is what "row-poly" means: a record can be passed into the function above as long as it contains a field `.x`. 30 | 31 | The type of the function is: 32 | 33 | ```purescript 34 | { x: a | l } -> a 35 | -- The label `l` represents the rest fields of a record. 36 | ``` 37 | 38 | Now you can do the same(not exactly) in rust. 39 | 40 | ## Usage 41 | 42 | restrict the parameter `r` contains a field `.x` 43 | 44 | ```rust 45 | fn take_x(r: row! { x: T, .. }) -> T { 46 | r.view(optics!(x)) 47 | } 48 | 49 | // &row! { x: T, .. } is ok 50 | fn take_x_ref(r: &row! { ref x: T, .. }) -> &T { 51 | r.view_ref(optics!(x)) 52 | } 53 | 54 | // &mut row! { x: T, .. } is ok 55 | fn take_x_mut(r: &mut row! { mut x: T, .. }) -> &mut T { 56 | r.view_mut(optics!(x)) 57 | } 58 | 59 | let foo = Foo { // have been derived Lens for Foo 60 | x: String::from("this is Foo"), 61 | y: 1234 62 | } 63 | 64 | let bar = Bar { // have been derived Lens for Bar 65 | x: 0, 66 | z: Some(1) 67 | } 68 | 69 | assert_eq!(&*take_x_ref(&foo), "this is Foo"); 70 | assert_eq!(take_x(bar), 0); 71 | ``` 72 | 73 | You can also describe a type *may* have a field: 74 | 75 | ```rust 76 | fn or_take_y(r: row! { y: T?, .. }) -> Option { 77 | r.preview(optics!(y)) 78 | } 79 | 80 | assert_eq!(or_take_y(foo), Some(1234)); 81 | assert_eq!(or_take_y(bar), None); 82 | ``` 83 | 84 | 85 | ## Desugar 86 | 87 | The function `take_x` is equivalent to 88 | 89 | ```rust 90 | fn take_x(r: R) -> T 91 | where 92 | R: Lens 93 | { 94 | r.view(optics!(x)) 95 | } 96 | ``` 97 | 98 | In fact the `row! { .. }` will be desugared to the impl trait, the placeholder of a type satisfied the lens trait. 99 | And the `dyn_row! { .. }` will be desugared to the dyn trait, the dynamic version of `row!`. 100 | 101 | ```rust 102 | fn sum_field(n: i32) -> Box { 103 | match n%3 { 104 | 0 => Box::new(Result::<_, String>::Ok(0)), 105 | 1 => Box::new(Result::::Err(String::from("no!"))), 106 | 2 => Box::new(Some(())), 107 | _ => Box::new(Option::<()>::None) 108 | } 109 | } 110 | ``` 111 | 112 | ## Limitations 113 | 114 | * Cannot pass the genreric arguments explicitly into the function when `row!` is used in argument position now, 115 | because the `row!` is the impl trait. 116 | * `dyn_row!` will lose some polymorphism e.g. `dyn_row! { x: i32, .. }` does not satisfy `dyn_row! { ref x: i32, .. }`, 117 | because the trait object cannot convert to the others, thought Trait1: Trait2. 118 | * Cannot move out of a field from a `dyn_row!`. 119 | 120 | ## Cargo.toml 121 | 122 | Please add the following in your Cargo.toml 123 | 124 | ```toml 125 | [dependencies] 126 | lens-rs = "0.3" 127 | rovv = "0.2" 128 | 129 | [package.metadata.inwelling] 130 | lens-rs_generator = true 131 | rovv = true 132 | ``` 133 | 134 | Enjoy it! -------------------------------------------------------------------------------- /rovv/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Overview 2 | //! 3 | //! `rovv` is a crate to provide a "poor-man's" row-polymorphism for rust base on [`lens-rs`](https://crates.io/crates/lens-rs). 4 | //! 5 | //! ## What is row poly? 6 | //! 7 | //! > In programming language type theory, row polymorphism is a kind of polymorphism that allows one to write programs that are polymorphic on record field types (also known as rows, hence row polymorphism). 8 | //! > 9 | //! > -- wikipedia 10 | //! 11 | //! Considering a function in PureScript: 12 | //! 13 | //! ```purescript 14 | //! \r -> r.x 15 | //! ``` 16 | //! 17 | //! you can pass a record into the function above 18 | //! 19 | //! ```purescript 20 | //! { x: 1 } 21 | //! ``` 22 | //! 23 | //! or even 24 | //! 25 | //! ```purescript 26 | //! { x: 1, y: 2, z: 3 } 27 | //! ``` 28 | //! 29 | //! That is what "row-poly" means: a record can be passed into the function above as long as it contains a field `.x`. 30 | //! 31 | //! The type of the function is: 32 | //! 33 | //! ```purescript 34 | //! { x: a | l } -> a 35 | //! -- The label `l` represents the rest fields of a record. 36 | //! ``` 37 | //! 38 | //! Now you can do the same(not exactly) in rust. 39 | //! 40 | //! ## Usage 41 | //! 42 | //! restrict the parameter `r` contains a field `.x` 43 | //! 44 | //! ```rust 45 | //! fn take_x(r: row! { x: T, .. }) -> T { 46 | //! r.view(optics!(x)) 47 | //! } 48 | //! 49 | //! // &row! { x: T, .. } is ok 50 | //! fn take_x_ref(r: &row! { ref x: T, .. }) -> &T { 51 | //! r.view_ref(optics!(x)) 52 | //! } 53 | //! 54 | //! // &mut row! { x: T, .. } is ok 55 | //! fn take_x_mut(r: &mut row! { mut x: T, .. }) -> &mut T { 56 | //! r.view_mut(optics!(x)) 57 | //! } 58 | //! 59 | //! let foo = Foo { // have been derived Lens for Foo 60 | //! x: String::from("this is Foo"), 61 | //! y: 1234 62 | //! } 63 | //! 64 | //! let bar = Bar { // have been derived Lens for Bar 65 | //! x: 0, 66 | //! z: Some(1) 67 | //! } 68 | //! 69 | //! assert_eq!(&*take_x_ref(&foo), "this is Foo"); 70 | //! assert_eq!(take_x(bar), 0); 71 | //! ``` 72 | //! 73 | //! You can also describe a type *may* have a field: 74 | //! 75 | //! ```rust 76 | //! fn or_take_y(r: row! { y: T?, .. }) -> Option { 77 | //! r.preview(optics!(y)) 78 | //! } 79 | //! 80 | //! assert_eq!(or_take_y(foo), Some(1234)); 81 | //! assert_eq!(or_take_y(bar), None); 82 | //! ``` 83 | //! 84 | //! 85 | //! ## Desugar 86 | //! 87 | //! The function `take_x` is equivalent to 88 | //! 89 | //! ```rust 90 | //! fn take_x(r: R) -> T 91 | //! where 92 | //! R: Lens 93 | //! { 94 | //! r.view(optics!(x)) 95 | //! } 96 | //! ``` 97 | //! 98 | //! In fact the `row! { .. }` will be desugared to the impl trait, the placeholder of a type satisfied the lens trait. 99 | //! And the `dyn_row! { .. }` will be desugared to the dyn trait, the dynamic version of `row!`. 100 | //! 101 | //! ```rust 102 | //! fn sum_field(n: i32) -> Box { 103 | //! match n%3 { 104 | //! 0 => Box::new(Result::<_, String>::Ok(0)), 105 | //! 1 => Box::new(Result::::Err(String::from("no!"))), 106 | //! 2 => Box::new(Some(())), 107 | //! _ => Box::new(Option::<()>::None) 108 | //! } 109 | //! } 110 | //! ``` 111 | //! 112 | //! ## Limitations 113 | //! 114 | //! * Cannot pass the genreric arguments explicitly into the function when `row!` is used in argument position now, 115 | //! because the `row!` is the impl trait. 116 | //! * `dyn_row!` will lose some polymorphism e.g. `dyn_row! { x: i32, .. }` does not satisfy `dyn_row! { ref x: i32, .. }`, 117 | //! because the trait object cannot convert to the others, thought Trait1: Trait2. 118 | //! * Cannot move out of a field from a `dyn_row!`. 119 | //! 120 | //! ## Cargo.toml 121 | //! 122 | //! Please add the following in your Cargo.toml 123 | //! 124 | //! ```toml 125 | //! [dependencies] 126 | //! lens-rs = "0.3" 127 | //! rovv = "0.2" 128 | //! 129 | //! [package.metadata.inwelling] 130 | //! lens-rs_generator = true 131 | //! rovv = true 132 | //! ``` 133 | //! 134 | //! Enjoy it! 135 | //! 136 | //! # License 137 | //! 138 | //! Under Apache License 2.0 or MIT License, at your will. 139 | 140 | /// the dynamic version of `row!` 141 | pub use rovv_derive::dyn_row; 142 | /// syntax: 143 | /// 144 | /// ```rust 145 | /// // .view(optics!(a)) 146 | /// // .preview(optics!(b)) 147 | /// // .traverse(optics!(c)) 148 | /// row! { a: A, b: B?, c: C*, .. } 149 | /// 150 | /// // control the mutability 151 | /// row! { a: A, ref b: B, mut c: C, .. } 152 | /// 153 | /// // with some bounds 154 | /// row! { a: A, .. : Debug + Clone + 'a } 155 | /// 156 | /// // pass an optic as the key, e.g. K = Optics![a.b] or even a generic argument 157 | /// row! { [K]: V, .. } 158 | /// ``` 159 | /// 160 | /// 161 | pub use rovv_derive::row; 162 | 163 | 164 | include!(concat!(env!("OUT_DIR"), "/dyn_row.rs")); 165 | -------------------------------------------------------------------------------- /rovv_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rovv_derive" 3 | version = "0.2.0" 4 | authors = ["Xyzt Toe <584605539@qq.com>"] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | readme = "readme.md" 8 | keywords = [ "anonymous", "structural", "polymorphism", "row"] 9 | categories = [ "rust-patterns" ] 10 | repository = "https://github.com/TOETOE55/rovv" 11 | description = "provide the anonymous row type (poor-man's row polymorphism) in rust" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | proc-macro2 = "1.0" 20 | quote = "1.0" 21 | syn = { version = "1.0", features = ["extra-traits","full","visit","visit-mut"] } 22 | 23 | -------------------------------------------------------------------------------- /rovv_derive/readme.md: -------------------------------------------------------------------------------- 1 | # rovv_derive 2 | 3 | parse `row!` and `dyn_row!`, and transform them to 4 | * `impl LensRef + LensMut + Lens` 5 | * `dyn LensRef + LensMut + Lens` -------------------------------------------------------------------------------- /rovv_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use quote::*; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | parse_macro_input, 6 | punctuated::Punctuated, 7 | Token, 8 | }; 9 | 10 | #[derive(Clone, Debug)] 11 | enum Mutability { 12 | Ref(Token![ref]), 13 | Mut(Token![mut]), 14 | Move, 15 | } 16 | 17 | #[derive(Clone, Debug)] 18 | enum TypeSuffix { 19 | Empty, 20 | Star(Token![*]), 21 | Question(Token![?]), 22 | } 23 | 24 | #[derive(Clone, Debug)] 25 | enum Key { 26 | Ident(syn::Ident), 27 | Type { 28 | _bracket_token: syn::token::Bracket, 29 | key_type: syn::Type, 30 | }, 31 | } 32 | 33 | /// ref a: A?, mut b: B*, c: C, [K]: V 34 | #[derive(Clone, Debug)] 35 | struct RowTypeField { 36 | mutability: Mutability, 37 | key: Key, 38 | _colon_token: Token![:], 39 | field_type: syn::Type, 40 | suffix: TypeSuffix, 41 | } 42 | 43 | /// row! { a: A, b: B, c: C, .. : Trait1 + Trait2 + 'a } 44 | #[derive(Clone, Debug)] 45 | struct RowType { 46 | fields: Punctuated, 47 | _dot2token: Token![..], 48 | _colon_token: Option, 49 | bounds: Punctuated, 50 | } 51 | 52 | impl Parse for Mutability { 53 | fn parse(input: ParseStream) -> syn::Result { 54 | let lookahead = input.lookahead1(); 55 | if lookahead.peek(Token![ref]) { 56 | Ok(Mutability::Ref(input.parse()?)) 57 | } else if lookahead.peek(Token![mut]) { 58 | Ok(Mutability::Mut(input.parse()?)) 59 | } else if lookahead.peek(syn::Ident) || lookahead.peek(syn::token::Bracket) { 60 | Ok(Mutability::Move) 61 | } else { 62 | Err(syn::Error::new( 63 | proc_macro2::Span::call_site(), 64 | "expected `ref`, `mut` or nothing", 65 | )) 66 | } 67 | } 68 | } 69 | 70 | impl Parse for TypeSuffix { 71 | fn parse(input: ParseStream) -> syn::Result { 72 | let lookahead = input.lookahead1(); 73 | if lookahead.peek(Token![*]) { 74 | Ok(TypeSuffix::Star(input.parse()?)) 75 | } else if lookahead.peek(Token![?]) { 76 | Ok(TypeSuffix::Question(input.parse()?)) 77 | } else { 78 | Ok(TypeSuffix::Empty) 79 | } 80 | } 81 | } 82 | 83 | impl Parse for Key { 84 | fn parse(input: ParseStream) -> syn::Result { 85 | if input.peek(syn::token::Bracket) { 86 | let content; 87 | let _bracket_token = syn::bracketed!(content in input); 88 | Ok(Self::Type { 89 | _bracket_token, 90 | key_type: content.parse()?, 91 | }) 92 | } else { 93 | Ok(Self::Ident(input.parse()?)) 94 | } 95 | } 96 | } 97 | 98 | impl ToTokens for Key { 99 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 100 | match self { 101 | Key::Ident(id) => tokens.extend(quote! { lens_rs::Optics![#id] }), 102 | Key::Type { key_type, .. } => key_type.to_tokens(tokens), 103 | } 104 | } 105 | } 106 | 107 | impl Parse for RowTypeField { 108 | fn parse(input: ParseStream) -> syn::Result { 109 | Ok(Self { 110 | mutability: input.parse()?, 111 | key: input.parse()?, 112 | _colon_token: input.parse()?, 113 | field_type: input.parse()?, 114 | suffix: input.parse()?, 115 | }) 116 | } 117 | } 118 | 119 | impl Parse for RowType { 120 | fn parse(input: ParseStream) -> syn::Result { 121 | let mut fields = Punctuated::new(); 122 | while !input.is_empty() && !input.peek(Token![..]) { 123 | let row_field = input.call(RowTypeField::parse)?; 124 | fields.push_value(row_field); 125 | if input.is_empty() { 126 | break; 127 | } 128 | let punct: Token![,] = input.parse()?; 129 | fields.push_punct(punct); 130 | } 131 | 132 | let _dot2token = if fields.empty_or_trailing() && input.peek(Token![..]) { 133 | input.parse()? 134 | } else { 135 | return Err(syn::Error::new( 136 | proc_macro2::Span::call_site(), 137 | "expected `..` token", 138 | )); 139 | }; 140 | 141 | let _colon_token = if input.peek(Token![:]) { 142 | Some(input.parse()?) 143 | } else { 144 | return Ok(Self { 145 | fields, 146 | _dot2token, 147 | _colon_token: None, 148 | bounds: Default::default(), 149 | }); 150 | }; 151 | 152 | Ok(Self { 153 | fields, 154 | _dot2token, 155 | _colon_token, 156 | bounds: Punctuated::parse_terminated(&input)?, 157 | }) 158 | } 159 | } 160 | 161 | /// transform 162 | /// 163 | /// ```rust 164 | /// row! { ref a: A, mut b: B, c: C, .. : Trait1 + Trait2 + 'a } 165 | /// ``` 166 | /// 167 | /// to 168 | /// 169 | /// ```rust 170 | /// impl LensRef + LensMut + Lens + Trait1 + Trait2 + 'a 171 | /// ``` 172 | #[proc_macro] 173 | pub fn row(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 174 | let row_type = parse_macro_input!(input as RowType); 175 | let fields: Vec = row_type.fields.into_iter().collect::>(); 176 | let (row_name, key, fields_ty, _) = join_row_field(fields); 177 | let row_ident = syn::Ident::new(&row_name, Span::call_site()); 178 | let bounds = row_type.bounds.into_iter().collect::>(); 179 | 180 | proc_macro::TokenStream::from(quote! { 181 | impl rovv::#row_ident<#(#key, #fields_ty),*> #(+ #bounds)* 182 | }) 183 | } 184 | 185 | /// transform 186 | /// 187 | /// ```rust 188 | /// dyn_row! { ref a: A, mut b: B, c: C, .. : Trait1 + Trait2 + 'a } 189 | /// ``` 190 | /// 191 | /// to 192 | /// 193 | /// ```rust 194 | /// dyn LensRef + LensMut + Lens + Trait1 + Trait2 + 'a 195 | /// ``` 196 | #[proc_macro] 197 | pub fn dyn_row(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 198 | let row_type = parse_macro_input!(input as RowType); 199 | let fields: Vec = row_type.fields.into_iter().collect::>(); 200 | let (row_name, key, fields_ty, _) = join_row_field(fields); 201 | let row_ident = syn::Ident::new(&row_name, Span::call_site()); 202 | let bounds = row_type.bounds.into_iter().collect::>(); 203 | 204 | proc_macro::TokenStream::from(quote! { 205 | dyn rovv::#row_ident<#(#key, #fields_ty),*> #(+ #bounds)* 206 | }) 207 | } 208 | 209 | fn join_row_field( 210 | mut fields: Vec, 211 | ) -> (String, Vec, Vec, Vec) { 212 | fields.sort_by_key(|field| map_trait(&field.suffix, &field.mutability)); 213 | 214 | let mut row_name = "_row".to_string(); 215 | let mut fields_key = Vec::new(); 216 | let mut fields_ty = Vec::new(); 217 | let mut optics_trait = Vec::new(); 218 | for field in fields { 219 | let trait_name = map_trait(&field.suffix, &field.mutability); 220 | row_name += &format!("_{}_", trait_name); 221 | optics_trait.push(syn::Ident::new(trait_name, Span::call_site())); 222 | fields_ty.push(field.field_type); 223 | fields_key.push(field.key); 224 | } 225 | 226 | (row_name, fields_key, fields_ty, optics_trait) 227 | } 228 | 229 | fn map_trait(suffix: &TypeSuffix, mutability: &Mutability) -> &'static str { 230 | match (suffix, mutability) { 231 | (TypeSuffix::Empty, Mutability::Ref(_)) => "LensRef", 232 | (TypeSuffix::Empty, Mutability::Mut(_)) => "LensMut", 233 | (TypeSuffix::Empty, Mutability::Move) => "Lens", 234 | (TypeSuffix::Star(_), Mutability::Ref(_)) => "TraversalRef", 235 | (TypeSuffix::Star(_), Mutability::Mut(_)) => "TraversalMut", 236 | (TypeSuffix::Star(_), Mutability::Move) => "Traversal", 237 | (TypeSuffix::Question(_), Mutability::Ref(_)) => "PrismRef", 238 | (TypeSuffix::Question(_), Mutability::Mut(_)) => "PrismMut", 239 | (TypeSuffix::Question(_), Mutability::Move) => "Prism", 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /rovv_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rovv_test" 3 | version = "0.1.0" 4 | authors = ["Xyzt Toe <584605539@qq.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | lens-rs = "0.3" 11 | rovv = { path = "../rovv" } 12 | 13 | [package.metadata.inwelling] 14 | lens-rs_generator = true 15 | rovv = true -------------------------------------------------------------------------------- /rovv_test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use lens_rs::*; 4 | // use structx::*; 5 | use rovv::*; 6 | use std::ops::Deref; 7 | 8 | #[derive(Copy, Clone, Debug, Lens)] 9 | struct Foo { 10 | #[optic] 11 | a: A, 12 | #[optic] 13 | b: B, 14 | } 15 | 16 | #[derive(Clone, Debug, Lens)] 17 | struct Bar { 18 | #[optic] 19 | a: String, 20 | #[optic] 21 | c: i32, 22 | } 23 | 24 | fn with_field_a(t: row! { a: String, .. }) -> String { 25 | t.view(optics!(a)) 26 | } 27 | 28 | fn with_field_ref_a(t: &row! { ref a: String, .. : ?Sized }) -> &str { 29 | t.view_ref(optics!(a)) 30 | } 31 | 32 | fn _with_field_mut_a(t: &mut row! { mut a: String, .. }) { 33 | *t.view_mut(optics!(a)) += "suffix"; 34 | } 35 | 36 | fn dyn_with_field_ref_a(r: &dyn_row! { a: String, .. }) -> &str { 37 | r.view_ref(optics!(a)) 38 | } 39 | 40 | fn to_field_a() -> row! { a: String, .. } { 41 | Bar { 42 | a: "this is Bar".to_string(), 43 | c: 1, 44 | } 45 | } 46 | 47 | fn to_dyn_field_a() -> Box { 48 | Box::new(Foo { 49 | a: "this is Foo".to_string(), 50 | b: Some(0), 51 | }) 52 | } 53 | 54 | fn may_with_field_c(t: &row! { c: i32?, .. }) -> Option { 55 | Some(*t.preview_ref(optics!(c))?) 56 | } 57 | 58 | fn row_with_bound(r: &row! { a: String, .. : Clone }) -> row! { a: String, .. : Clone } { 59 | r.clone() 60 | } 61 | 62 | // function foo(key: keyof Bar, r: { [K in keyof Bar]: number }): number 63 | fn row_keyof(key: K, r: &row! { [K]: i32, .. }) -> i32 64 | where 65 | Bar: Lens, 66 | { 67 | *r.view_ref(key) 68 | } 69 | 70 | fn sum_field(n: i32) -> Box { 71 | match n % 3 { 72 | 0 => Box::new(Result::<_, String>::Ok(0)), 73 | 1 => Box::new(Result::::Err(String::from("no!"))), 74 | 2 => Box::new(Some(())), 75 | _ => Box::new(Option::<()>::None), 76 | } 77 | } 78 | 79 | #[test] 80 | fn test_row() { 81 | let foo = Foo { 82 | a: "this is Foo".to_string(), 83 | b: (), 84 | }; 85 | let bar = Bar { 86 | a: "this is Bar".to_string(), 87 | c: 0, 88 | }; 89 | 90 | assert_eq!(&*with_field_a(foo.clone()), "this is Foo"); 91 | assert_eq!(&*with_field_a(bar.clone()), "this is Bar"); 92 | 93 | assert_eq!(with_field_ref_a(&foo), "this is Foo"); 94 | assert_eq!(with_field_ref_a(&bar), "this is Bar"); 95 | 96 | assert_eq!(may_with_field_c(&bar), Some(0)); 97 | assert_eq!(may_with_field_c(&foo), None); 98 | 99 | assert_eq!(*row_with_bound(&foo).view_ref(optics!(a)), "this is Foo"); 100 | 101 | assert_eq!(&*with_field_a(to_field_a()), "this is Bar"); 102 | assert_eq!(with_field_ref_a(to_dyn_field_a().deref()), "this is Foo"); 103 | assert_eq!( 104 | dyn_with_field_ref_a(to_dyn_field_a().deref()), 105 | "this is Foo" 106 | ); 107 | 108 | assert_eq!(row_keyof(optics!(a), &Foo { a: 1, b: () }), 1); 109 | } 110 | } 111 | --------------------------------------------------------------------------------