├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── structx ├── Cargo.toml ├── README.md ├── build.rs └── src │ └── lib.rs ├── structx_derive ├── Cargo.toml ├── README.md └── src │ └── lib.rs └── structx_test ├── Cargo.toml ├── build.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swo 2 | .*.swp 3 | .swp 4 | /target 5 | **/*.rs.bk 6 | Cargo.lock 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "structx", 4 | "structx_derive", 5 | "structx_test", 6 | ] 7 | resolver = "2" 8 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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. 202 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [summary]: #summary 4 | 5 | **Provides _structural records_** of the form `structx!{ foo: 1u8, bar: true }` 6 | of type `Structx!{ foo: u8, bar: bool }`. Another way to understand these sorts 7 | of objects is to think of them as *"tuples with named fields"*, 8 | *"unnamed structs"*, or *"anonymous structs"*. 9 | 10 | # Motivation 11 | 12 | [motivation]: #motivation 13 | 14 | There are four motivations to introduce structural records, two major and two 15 | minor. 16 | 17 | ## Major: Improving ergonomics, readability, and maintainability 18 | 19 | Sometimes, you just need a one-off struct that you will use a few times for some 20 | public API or in particular for some internal API. For example, you'd like to 21 | send over a few values to a function, but you don't want to have too many 22 | function arguments. In such a scenario, defining a new nominal `struct` which 23 | contains all the fields you want to pass is not a particularly ergonomic 24 | solution. 25 | 26 | Instead, it is rather heavy weight: 27 | 28 | ```rust 29 | struct Color { 30 | red : u8, 31 | green : u8, 32 | blue : u8, 33 | } 34 | 35 | fn caller() { 36 | ... 37 | let color = Color{ 38 | red: 255, green: 0, blue: 0 39 | }; 40 | ... 41 | do_stuff_with( color ); 42 | ... 43 | } 44 | 45 | // Only time we use `Color`! 46 | fn do_stuff_with( color: Color ) { 47 | some_stuff( color.red ); 48 | ... 49 | other_stuff( color.green ); 50 | ... 51 | yet_more_stuff( color.blue ); 52 | } 53 | ``` 54 | 55 | To remedy the ergonomics problem, you may instead opt for using a positional 56 | tuple to contain the values you want to pass. However, now you have likely 57 | created a regression for readers of your code since the fields of tuples are 58 | accessed with their positional index, which does not carry clear semantic 59 | intent: 60 | 61 | ```rust 62 | fn caller() { 63 | ... 64 | let color = (255, 0, 0); 65 | ... 66 | // Unclear what each position means... 67 | do_stuff_with( color ); 68 | ... 69 | // More ergonomic! 70 | fn do_stuff_with(color: (u8, u8, u8)) { 71 | // But less readable... :( 72 | some_stuff( color.0 ); 73 | ... 74 | other_stuff( color.1 ); 75 | ... 76 | yet_more_stuff( color.2 ); 77 | } 78 | ``` 79 | 80 | Using structural records, we can have our cake and eat it too: 81 | 82 | ```rust 83 | use structx::*; 84 | 85 | fn caller() { 86 | ... 87 | let color = structx!{ red: 255, green: 0, blue: 0 }; 88 | ... 89 | // ...but here it is clear. 90 | do_stuff_with( color ); 91 | ... 92 | } 93 | 94 | // More ergonomic! 95 | fn do_stuff_with( color: structx!{ red: u8, green: u8, blue: u8 }) { 96 | // *And* readable! :) 97 | some_stuff( color.red ); 98 | ... 99 | other_stuff( color.green ); 100 | ... 101 | yet_more_stuff( color.blue ); 102 | } 103 | ``` 104 | 105 | In the above snippet, the semantic intent of the fields is clear both when 106 | reading the body of the function, as well as when reading the documentation 107 | when `do_stuff_with` is exposed as a public API. 108 | 109 | [@eternaleye]: https://internals.rust-lang.org/t/pre-rfc-unnamed-struct-types/3872/58 110 | [@kardeiz]: https://internals.rust-lang.org/t/pre-rfc-unnamed-struct-types/3872/65 111 | 112 | Another example of reducing boilerplate was given by [@eternaleye]: 113 | 114 | ```rust 115 | use structx::*; 116 | 117 | struct RectangleClassic { 118 | width : u64, 119 | height : u64, 120 | red : u8, 121 | green : u8, 122 | blue : u8, 123 | } 124 | 125 | struct RectangleTidy { 126 | dimensions : Structx!{ 127 | width : u64, 128 | height : u64, 129 | }, 130 | color : Structx!{ 131 | red : u8, 132 | green : u8, 133 | blue : u8, 134 | }, 135 | } 136 | ``` 137 | 138 | In the second type `RectangleTidy`, we keep boilerplate to a minimum and we can 139 | also treat `rect.color` and `rect.dimensions` as separate objects and move them 140 | out of `rect : RectangleTidy` as units, which we cannot do with 141 | `RectangleClassic`. 142 | 143 | If we wanted to do that, we would have to invent two new types `Dimensions` and 144 | `Color` and then `#[derive(..)]` the bits and pieces that we need. 145 | 146 | As noted by [@kardeiz], this ability may also be useful for serializing one-off 147 | structures with `serde`. 148 | 149 | ## Major: Better rapid prototyping and refactoring 150 | 151 | Let's assume we opted for using the type 152 | `Structx!{ red: u8, green: u8, blue: u8 }` from above. This gave us the ability 153 | to prototype our application rapidly. 154 | 155 | However, as time passes, we might have more uses for RGB colours and so we 156 | decide to make a nominal type out of it and give it some operations. 157 | 158 | Because the structural record we've used above has named fields, we can easily 159 | refactor it into the nominal type `Color`. 160 | 161 | Indeed, an IDE should be able to use the information that exists and provide the 162 | refactoring for you automatically. If we had instead used a positional tuple, 163 | the information would simply be unavailable. Thus, the refactoring could not be 164 | made automatically and if you had to do it manually, you would need to spend 165 | time understanding the code to deduce what proper field names would be. 166 | 167 | ## Minor: Emulating named function arguments 168 | 169 | Structural records could be considered to lessen the need for named function 170 | arguments by writing in the following style: 171 | 172 | ```rust 173 | use structx::*; 174 | 175 | fn foo( bar: Structx!{ baz: u8, quux: u8 }) -> u8 { 176 | bar.baz + bar.quux 177 | } 178 | 179 | fn main() { 180 | assert_eq!( 3, foo(structx!{ baz: 1, quux: 2 })); 181 | } 182 | ``` 183 | 184 | Using some macros to make it more clear: 185 | 186 | ```rust 187 | use structx::*; 188 | use structx::named_args::*; 189 | 190 | #[named_args] 191 | fn foo( baz: u8, quux: u8 ) -> u8 { 192 | bar.baz + bar.quux 193 | } 194 | 195 | fn main() { 196 | assert_eq!( 3, foo( args!{ baz: 1, quux: 2 })); 197 | } 198 | ``` 199 | 200 | While this is a possible use case, in this crate, we do not see named function 201 | arguments as a *major* motivation for structural records as they do not cover 202 | aspects of named arguments that people sometimes want. In particular: 203 | 204 | 1. With structural records, you cannot construct them positionally. 205 | 206 | In other words, you may not call `foo` from above with `foo(1, 2)` because these 207 | records do not have a defined order that users can make use of. 208 | 209 | [`<*const T>::copy_to_nonoverlapping`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to_nonoverlapping 210 | 211 | 2. You cannot adapt existing standard library functions to use named arguments. 212 | 213 | Consider for example the function [`<*const T>::copy_to_nonoverlapping`]. It has 214 | the following signature: 215 | 216 | ```rust 217 | pub unsafe fn copy_to_nonoverlapping( self, dest: *mut T, count: usize ); 218 | ``` 219 | 220 | Because this is an `unsafe` function, we might want to call this as: 221 | 222 | ```rust 223 | ptr.copy_to_nonoverlapping( args!{ dest: , count: }) 224 | ``` 225 | 226 | However, because we can write: 227 | 228 | ```rust 229 | const X: unsafe fn( *const u8, *mut u8, usize ) = <*const u8>::copy_to_nonoverlapping; 230 | ``` 231 | 232 | it would be a breaking change to redefine the function to take a structural 233 | record instead of two arguments. 234 | 235 | Having noted these two possible deficiencies of structural records as a way to 236 | emulate named function arguments, this emulation can still work well in many 237 | cases. Thus, while the motivation is not major here, we still consider it to be 238 | a minor motivation. 239 | 240 | ## Minor: Smoothing out the language 241 | 242 | The current situation in the language with respect to product types can be 243 | described with the following table: 244 | 245 | | | Nominal | Structural | 246 | |------------------|--------------------------------|--------------------| 247 | | **Unit** | Yes, `struct T;` | Yes, `()` | 248 | | **Positional** | Yes, `struct T(A, B);` | Yes, `(A, B)` | 249 | | **Named fields** | Yes, `struct T { a: A, b: B }` | **No**, this crate | 250 | 251 | As we can see, the current situation is inconsistent. 252 | 253 | While the language provides for unit types and positional product types of both 254 | the nominal and structural flavour, the structural variant of structs with named 255 | fields is missing while the nominal type exists. 256 | 257 | A consistent programming language is a beautiful language, but it's not an end 258 | in itself. Instead, the main benefit is to reduce surprises for learners. 259 | 260 | [@withoutboats]: https://internals.rust-lang.org/t/pre-rfc-unnamed-struct-types/3872/23 261 | [@regexident]: https://internals.rust-lang.org/t/pre-rfc-catching-functions/6505/188 262 | 263 | Indeed, [@withoutboats] noted: 264 | 265 | > To me this seems consistent and fine - the kind of feature a user could infer 266 | to exist from knowing the other features of Rust - but I’m not thrilled by the 267 | idea of trying to use this to implement named arguments. 268 | 269 | # Guide-level explanation 270 | 271 | [guide-level-explanation]: #guide-level-explanation 272 | 273 | ## Vocabulary 274 | 275 | [structural typing]: https://en.wikipedia.org/wiki/Structural_type_system 276 | [nominal typing]: https://en.wikipedia.org/wiki/Nominal_type_system 277 | [product type]: https://en.wikipedia.org/wiki/Product_type 278 | [record type]: https://en.wikipedia.org/wiki/Record_(computer_science) 279 | 280 | - With *[structural typing]*, the equivalence of two types is determined by 281 | looking at their structure. For example, given the type `(A, B)` and `(C, D)` 282 | where `A`, `B`, `C`, and `D` are types, if `A == C` and `B == D`, then 283 | `(A, B) == (C, D)`. In Rust, the only case of structural typing is the 284 | positional tuples we've already seen. 285 | 286 | The main benefit of structural typing is that it becomes ergonomic and quite 287 | expressive to produce new types *"out of thin air"* without having to predefine 288 | each type. 289 | 290 | - With *[nominal typing]*, types are instead equivalent if their declared names 291 | are the same. For example, assuming that the types `Foo` and `Bar` are defined 292 | as: 293 | 294 | ```rust 295 | struct Foo( u8, bool ); 296 | struct Bar( u8, bool ); 297 | ``` 298 | 299 | even though they have the same structure, `Foo` is not the same type as `Bar` 300 | and so the type system will not let you use a `Foo` where a `Bar` is expected. 301 | 302 | The main benefit to nominal typing is maintainability and robustness. In Rust, 303 | we take advantage of nominal typing coupled with privacy to enforce API 304 | invariants. This is particularly important for code that involves use of 305 | `unsafe`. 306 | 307 | - A *[product type]* is a type which is made up of a sequence of other types. 308 | 309 | For example, the type `(A, B, C)` consists of the types `A`, `B`, *and* `C`. 310 | They are called product types because the number of possible values that the 311 | type can take is the *product* of the number of values that each component / 312 | factor / operand type can take. For example, if we consider `(A, B, C)`, the 313 | number of values is `values(A) * values(B) * values(C)`. 314 | 315 | - A *[record type]* is a special case of a product type in which each component 316 | 317 | type is *labeled*. In Rust, record types are `struct`s with named fields. 318 | 319 | For example, you can write: 320 | 321 | ```rust 322 | struct Foo { 323 | bar : u8, 324 | baz : bool, 325 | } 326 | ``` 327 | 328 | The only case of a record type in Rust uses nominal typing. There is currently 329 | no structural variant of record types. This brings us to this crate... 330 | 331 | ## Feature 332 | 333 | As we've seen, Rust currently lacks a structurally typed variant of record types. 334 | 335 | In this crate, we propose to change this by providing predefined record types that seems also structural, or in other words: *"structural records"*. 336 | 337 | ### Construction 338 | 339 | To create a structural record with the field `alpha` with value `42u8`, `beta` 340 | with value `true`, and `gamma` with value `"Dancing Ferris"`, you can simply 341 | write: 342 | 343 | ```rust 344 | use structx::*; 345 | 346 | let my_record = structx!{ 347 | alpha : 42u8, 348 | beta : true, 349 | gamma : "Dancing Ferris", 350 | }; 351 | ``` 352 | 353 | Note how this is the same syntax as used for creating normal structs but without 354 | the name of the struct. So we have taken: 355 | 356 | ```rust 357 | use structx::*; 358 | 359 | struct MyRecordType { 360 | alpha : u8, 361 | beta : bool, 362 | gamma : &'static str, 363 | } 364 | 365 | let my_record_nominal = MyRecordType { 366 | alpha : 42u8, 367 | beta : true, 368 | gamma : "Dancing Ferris", 369 | }; 370 | ``` 371 | 372 | and removed `MyRecordType`. Note that because we are using structural typing, 373 | we did not have to define a type `MyRecordType` ahead of time. If you already 374 | had variables named `alpha` and `beta` in scope, just as you could have with 375 | `MyRecordType`, you could have also written: 376 | 377 | ```rust 378 | use structx::*; 379 | 380 | let alpha = 42u8; 381 | let beta = true; 382 | let my_record = structx!{ 383 | alpha, 384 | beta , 385 | gamma: "Dancing Ferris", 386 | }; 387 | ``` 388 | 389 | ### Pattern matching 390 | 391 | Once you have produced a structural record, you can also pattern match on the 392 | expression. To do so, you can write: 393 | 394 | ```rust 395 | match my_record { 396 | structx!{ alpha, beta, gamma } => 397 | println!( "{}, {}, {}", alpha, beta, gamma ), 398 | } 399 | ``` 400 | 401 | This pattern is *irrefutable* so you can also just write: 402 | 403 | ```rust 404 | let structx!{ alpha, beta, gamma } = my_record; 405 | println!( "{}, {}, {}", alpha, beta, gamma ); 406 | ``` 407 | 408 | This is not particular to `match` and `let`. This also works for `if let`, 409 | `while let`, `for` loops, and function arguments. 410 | 411 | If we had used `MyRecordType`, you would have instead written: 412 | 413 | ```rust 414 | let MyRecordType{ alpha, beta, gamma } = my_record_nominal; 415 | println!( "{}, {}, {}", alpha, beta, gamma ); 416 | ``` 417 | 418 | When pattern matching on a structural record, it is also possible to give the 419 | binding you've created a different name. To do so, write: 420 | 421 | ```rust 422 | let structx!{ alpha: new_alpha, beta, gamma } = my_record; 423 | println!( "{}, {}, {}", new_alpha, beta, gamma ); 424 | ``` 425 | 426 | In this snippet, we've bound the field `alpha` to the binding `new_alpha`. 427 | This is not limited to one field, you can do this will all of them. 428 | Unlike nominal structs, it's impossible to ignore some or all fields when 429 | pattern matching on structural records: 430 | 431 | ```rust,compile_fail 432 | let structx!{ alpha, .. } = my_record; 433 | println!( "{}", alpha ); 434 | let structx!{ .. } = my_record; 435 | ``` 436 | 437 | ### Field access 438 | 439 | Given the binding `my_record_nominal` of type `MyRecordType`, you can access its 440 | fields with the usual `my_record_nomina.alpha` syntax. This also applies to 441 | structural records. It is perfectly valid to move or copy: 442 | 443 | ```rust 444 | println!( "The answer to life... is: {}", my_record.alpha ); 445 | ``` 446 | 447 | or to borrow a field: 448 | 449 | ```rust 450 | fn borrow( x: &bool ) { .. } 451 | borrow( &my_record.beta ); 452 | ``` 453 | 454 | including mutably: 455 | 456 | ```rust 457 | use structx::*; 458 | 459 | fn mutably_borrow( x: &mut bool ) { .. } 460 | let mut my_record = structx!{ alpha: 42u8, beta: true, gamma: "Dancing Ferris" }; 461 | mutably_borrow( &mut my_record.beta ); 462 | ``` 463 | 464 | # Struct update syntax 465 | 466 | Nominal structs support what is referred to as the "struct update syntax", 467 | otherwise known as functional record update (FRU). For example, you can write: 468 | 469 | ```rust 470 | struct Color { 471 | red : u8, 472 | green : u8, 473 | blue : u8, 474 | } 475 | 476 | let yellow = Color{ red: 0, green: 255, blue: 255 }; 477 | let white = Color{ red: 255, ..yellow }; 478 | ``` 479 | 480 | This also works for structural records, so you can write: 481 | 482 | ```rust 483 | let yellow = structx!{ red: 0, green: 255, blue: 255 }; 484 | let white = structx!{ red: 255, ..yellow }; 485 | ``` 486 | 487 | To match the behaviour of FRU for nominal structs, we impose a restriction that 488 | the fields mentioned before ..yellow must all exist in yellow and have the same 489 | types. This means that we cannot write: 490 | 491 | ```rust,compile_fail 492 | let white_50_opacity = structx!{ alpha: 0.5, red: 255, ..yellow }; 493 | ``` 494 | 495 | ### The type of a structural record 496 | 497 | You might be wondering what the type of `my_record` that we've been using thus 498 | far is. Because this is structural typing, the fields are significant, so the 499 | type of the record is simply: 500 | 501 | ```rust 502 | use structx::*; 503 | 504 | type TheRecord = Structx!{ 505 | alpha: u8, beta: bool, gamma: &'static str 506 | }; 507 | let my_record: TheRecord = structx!{ 508 | alpha: 42u8, beta: true, gamma: "Dancing Ferris" 509 | }; 510 | ``` 511 | 512 | Notice how this matches the way we defined `MyRecordType` is we remove the 513 | prefix `struct MyRecordType`. The order in which we've put `alpha`, `beta`, and 514 | `gamma` here does not matter. 515 | 516 | We could have also written: 517 | 518 | ```rust 519 | use structx::*; 520 | 521 | type TheRecord = structx!{ 522 | beta: bool, alpha: u8, gamma: &'static str 523 | }; 524 | ``` 525 | 526 | As long as the type is a *permutation* of each pair of field name and field 527 | type, the type is the same. This also means that we can write: 528 | 529 | ```rust 530 | use structx::*; 531 | 532 | let my_record: TheRecord = structx!{ 533 | alpha: 42u8, gamma: "Dancing Ferris", beta: true 534 | }; 535 | ``` 536 | 537 | ### Implemented traits 538 | 539 | With respect to trait implementations, because the type is structural, and 540 | because there may be an unbound number of fields that can all be arbitrary 541 | identifiers, there's no way to define implementations for the usual traits 542 | in the language itself. 543 | 544 | [tuples]: https://doc.rust-lang.org/std/primitive.tuple.html 545 | 546 | Instead, the compiler will automatically provide trait implementations for the 547 | standard traits that are implemented for [tuples]. These traits are: `Clone`, 548 | `Copy`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Debug`, `Default`, and `Hash`. 549 | Each of these traits will only be implemented if all the field types of a struct 550 | implements the trait. 551 | 552 | For all of the aforementioned standard traits, the semantics of the 553 | implementations are similar to that of `#[derive(Trait)]` for named-field 554 | structs. 555 | 556 | + For cloning `Structx!{ alpha: A, beta: B, gamma: C }` the logic is simply: 557 | 558 | ```rust 559 | structx!{ 560 | alpha : self.alpha.clone(), 561 | beta : self.beta .clone(), 562 | gamma : self.gamma.clone(), 563 | } 564 | ``` 565 | 566 | + For `Default`, you would get: 567 | 568 | ```rust 569 | structx!{ 570 | alpha : Default::default(), 571 | beta : Default::default(), 572 | gamma : Default::default(), 573 | } 574 | ``` 575 | 576 | + For `PartialEq`, each field is compared with same field in `other: Self`. 577 | 578 | + For `ParialOrd` and `Ord`, lexicographic ordering is used based on the name of 579 | the fields and not the order given because structural records don't respect the 580 | order in which the fields are put when constructing or giving the type of the 581 | record. 582 | 583 | + For `Debug` the same lexicographic ordering for `Ord` is used. As an example, 584 | when printing out `my_record` as with: 585 | 586 | ```rust 587 | use structx::*; 588 | 589 | let my_record = structx!{ 590 | beta: true, alpha: 42u8, gamma: "Dancing Ferris" 591 | }; 592 | println!( "{:#?}", my_record ); 593 | ``` 594 | 595 | the following would appear: 596 | 597 | ```rust 598 | structx!{ 599 | alpha : 42, 600 | beta : true, 601 | gamma : "Dancing Ferris", 602 | } 603 | ``` 604 | 605 | + For `Hash`, the same ordering of the fields as before is used and then 606 | `self.the_field.hash(state)` is called on each field in that order. 607 | 608 | For example: 609 | 610 | ```rust 611 | self.alpha.hash( state ); 612 | self.beta .hash( state ); 613 | self.gamma.hash( state ); 614 | ``` 615 | 616 | For auto traits (e.g. `Send`, `Sync`, `Unpin`), if all field types implement the 617 | auto trait, then the structural record does as well. For example, if `A` and `B` 618 | are `Send`, then so is `Structx!{ x: A, y: B }`. 619 | 620 | A structural record is `Sized` if all field types are. If the lexicographically 621 | last field of a structural record is `!Sized`, then so is the structural record. 622 | If any other field but the last is `!Sized`, then the type of the structural 623 | record is not well-formed. 624 | 625 | ### Implementations and orphans 626 | 627 | [RFC 2451]: https://github.com/rust-lang/rfcs/pull/2451 628 | 629 | It is possible to define your own implementations for a structural record. The 630 | orphan rules that apply here are those of [RFC 2451] by viewing a structural 631 | record as a positional tuple after sorting the elements lexicographically. 632 | 633 | For example, if a `trait` is crate-local, we may implement it for a record: 634 | 635 | ```rust 636 | use structx::*; 637 | 638 | trait Foo {} 639 | impl Foo for Structx!{ alpha: bool, beta: u8 } {} 640 | ``` 641 | 642 | Under 2451, we can also write: 643 | 644 | ```rust 645 | use structx::*; 646 | 647 | struct Local( T ); 648 | 649 | impl From<()> for Local { ... } 650 | ``` 651 | 652 | This is valid because `Local` is considered a local type. 653 | 654 | However, a structural record itself isn't a local type, so you cannot write: 655 | 656 | ```rust 657 | use structx::*; 658 | 659 | struct A; 660 | 661 | impl From<()> for Structx!{ alpha: A, beta: u8 } { ... } 662 | ``` 663 | 664 | This is the case even though `A` is a type local to the crate. 665 | 666 | The behaviour for inherent implementations is also akin to tuples. 667 | 668 | # Drawbacks 669 | 670 | [drawbacks]: #drawbacks 671 | 672 | ## Overuse? 673 | 674 | Nominal typing is a great thing. It offers robustness and encapsulation with 675 | which quantities that are semantically different but which have the same type 676 | can be distinguished statically. With privacy, you can also build APIs that 677 | make use of `unsafe` that you couldn't do with tuples or structural records. 678 | 679 | If structural records are overused, this may reduce the overall robustness 680 | of code in the ecosystem. However, we argue that structural records are more 681 | robust than positional tuples are and allow you to more naturally transition 682 | towards nominally typed records so the loss of robustness may be made up for 683 | by reduced usage of positional tuples. 684 | 685 | ## Hard to implement crate-external traits 686 | 687 | As with positional tuples, because a structural record is never crate local, 688 | this presents users with a problem when they need to implement a trait they 689 | don't own for a structural record comprising of crate-local types. 690 | 691 | For example, say that you have the crate-local types `Foo` and `Bar`. Both of 692 | these types implement `serde::Serialize`. Now you'd like to serialize 693 | `type T = { foo: Foo, bar: Bar };`. However, because neither `serde::Serialize` 694 | nor `T` is crate-local, you cannot `impl serde::Serialize for T { ... }`. 695 | 696 | This inability is both a good and a bad thing. The good part of it is that it 697 | might prevent overuse of structural records and provide some pressure towards 698 | nominal typing that might be good for robustness. The bad part is that these 699 | sort of one-off structures are a good reason to have structural records in the 700 | first place. With some combined quantification of field labels (possibly via 701 | const generics), and with tuple-variadic generics, it should be possible (for 702 | `serde`, if there is a will, to offer implementations of `Serialize` for all 703 | structural records. 704 | 705 | Note that while `impl serde::Serialize for T { ... }` may not be possible 706 | without extensions, the following would be: 707 | 708 | ```rust 709 | #[derive( Serialize )] 710 | struct RectangleTidy { 711 | dimensions: Structx!{ 712 | width: u64, 713 | height: u64, 714 | }, 715 | color: Structx!{ 716 | red: u8, 717 | green: u8, 718 | blue: u8, 719 | }, 720 | } 721 | ``` 722 | 723 | ## "Auto-implementing traits is a magical hack" 724 | 725 | Indeed, we would much prefer to use a less magical approach, but providing these 726 | traits without compiler magic would require significantly more complexity such 727 | as polymorphism over field names as well as variadic generics. Therefore, to 728 | make structural records usable in practice, providing a small set of traits with 729 | compiler magic is a considerably less complex approach. In the future, perhaps 730 | the magic could be removed. 731 | -------------------------------------------------------------------------------- /structx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structx" 3 | version = "0.1.11" 4 | authors = ["oooutlk "] 5 | edition = "2021" 6 | license = "MIT/Apache-2.0" 7 | keywords = [ "struct", "anonymous", "named", "arguments", "structural" ] 8 | readme = "README.md" 9 | repository = "https://github.com/oooutlk/structx" 10 | documentation = "https://docs.rs/structx" 11 | categories = [ "rust-patterns" ] 12 | description = "Simulating anonymous struct and named arguments in Rust." 13 | 14 | [build-dependencies] 15 | inwelling = "0.5" 16 | proc-macro2 = "1.0" 17 | quote = "1.0" 18 | syn = { version = "2.0", features = ["extra-traits","full","parsing","visit","visit-mut"] } 19 | 20 | [dependencies] 21 | structx_derive = { path = "../structx_derive", version = "0.1.4" } 22 | lens-rs = { version = "0.3", features = ["structx"], optional = true } 23 | -------------------------------------------------------------------------------- /structx/README.md: -------------------------------------------------------------------------------- 1 | See [RFC as Readme-For-Crate](https://github.com/oooutlk/structx/blob/main/README.md) 2 | for more. 3 | 4 | # Overview 5 | 6 | This project provides simulation of anonymous struct and named arguments in 7 | Rust, using proc macros `structx!{}`, `Structx!{}`, `#[named_args]` and 8 | `args!{}`. 9 | 10 | ## Usage of this crate 11 | 12 | Add the following in your Cargo.toml file: 13 | 14 | ```toml 15 | [dependencies] 16 | structx = "0.1" 17 | 18 | [build-dependencies] 19 | inwelling = "0.5" 20 | 21 | [package.metadata.inwelling] 22 | structx = true 23 | ``` 24 | 25 | Add the following in your build.rs file: 26 | 27 | ```rust 28 | inwelling::to( "structx" ); 29 | ``` 30 | 31 | Add the following in your .rs files: 32 | 33 | ```rust 34 | use structx::*; 35 | ``` 36 | 37 | If you want to use named arguments, add the following: 38 | 39 | ```rust 40 | use structx::named_args::*; 41 | ``` 42 | 43 | # Definitions and notations of anonymous structs 44 | 45 | Anonymous structs are `struct`s without the needs of providing struct names. 46 | However, the field names are mandatory. Anonymous structs are of the same type 47 | if and only if they are composed of the same set of field names. The order of 48 | fields are irrelevant. 49 | 50 | ## Value of anonymous structs 51 | 52 | The notation of an anonymous struct's value is `structx!{}`. 53 | 54 | ## Examples of anonymous struct's values 55 | 56 | ```rust 57 | let foo = structx!{ i: 3, b: true }; 58 | let bar = structx!{ x, y }; 59 | ``` 60 | 61 | ## Type of anonymous structs 62 | 63 | The notation of an anonymous struct's type is `Structx!{}`. 64 | 65 | ## Examples of anonymous struct's types 66 | 67 | ```rust 68 | fn foo( x: i32, y: i32 ) -> Structx!{ x: i32, y: i32 } { 69 | structx!{ x, y: y+1 } 70 | } 71 | ``` 72 | 73 | ## Traits derived for anonymous structs 74 | 75 | Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash. 76 | 77 | ```rust 78 | let a = structx!{ width : 800, height: 600 }; 79 | let b = structx!{ height: 600, width : 800 }; 80 | let c = structx!{ width : 1024, height: 768 }; 81 | assert_eq!( a, b ); 82 | assert_ne!( a, c ); 83 | ``` 84 | 85 | # Simulation of named arguments 86 | 87 | At definition site, add attributes `#[named_args]` to functions. 88 | 89 | ```rust 90 | #[named_args] 91 | fn set_size( width: u32, height: u32 ) { todo!() } 92 | ``` 93 | 94 | At call site, wrap arguments with `args!{}`. 95 | 96 | ```rust 97 | set_size( args!{ width: 1024, height: 768 }); 98 | ``` 99 | 100 | # License 101 | 102 | Under Apache License 2.0 or MIT License, at your will. 103 | -------------------------------------------------------------------------------- /structx/build.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; 2 | 3 | use quote::ToTokens; 4 | 5 | use std::{collections::HashMap, env, fs, path::PathBuf}; 6 | use syn::parse::{Parse, ParseStream}; 7 | use syn::spanned::Spanned; 8 | use syn::{ 9 | visit::{self, Visit}, 10 | Attribute, Error, Expr, ExprStruct, Field, FnArg, ItemFn, ItemStruct, Macro, Member, Meta, Pat, 11 | PatStruct, Type, Visibility, 12 | }; 13 | 14 | /* 15 | * Introduced a new type which can be parsed with syn::parse2. 16 | * This is necessary because syn version 2 doesn't implement Parse for Pat 17 | */ 18 | struct PatStructX(PatStruct); 19 | 20 | impl Parse for PatStructX { 21 | fn parse(input: ParseStream) -> syn::Result { 22 | let inner = Pat::parse_single(input)?; 23 | if let Pat::Struct(pat_struct) = inner { 24 | return Ok(PatStructX(pat_struct)); 25 | } 26 | Err(Error::new( 27 | inner.span(), 28 | "Unsupported pattern in structx macro!", 29 | )) 30 | } 31 | } 32 | 33 | // A new type which abstracts over a field value type 34 | enum FieldValue { 35 | Expr(Expr), 36 | Pat(Pat), 37 | Type(Type), 38 | } 39 | 40 | // StructX is now responsible for parsing the inner part of 41 | // a structx macro. 42 | enum StructX { 43 | Expr(ExprStruct), 44 | Item(ItemStruct), 45 | Pattern(PatStruct), 46 | } 47 | 48 | impl StructX { 49 | const STRUCT_NAME: &'static str = "StructX"; 50 | 51 | #[inline] 52 | fn has_vis(vis: &Visibility) -> bool { 53 | match vis { 54 | Visibility::Public(_) => true, 55 | Visibility::Restricted(_) => true, 56 | Visibility::Inherited => false, 57 | } 58 | } 59 | 60 | #[inline] 61 | fn field_has_vis(field: &Field) -> bool { 62 | Self::has_vis(&field.vis) 63 | } 64 | 65 | fn check_attrs(span: Span, attrs: &Vec) -> syn::Result<()> { 66 | if !attrs.is_empty() { 67 | return Err(Error::new(span, "Structx fields can't contain attributes!")); 68 | } 69 | Ok(()) 70 | } 71 | 72 | fn check_named(span: Span, member: &Member) -> syn::Result<()> { 73 | if let Member::Unnamed(_) = member { 74 | return Err(Error::new(span, "Structx can't contain unnamed fields!")); 75 | } 76 | Ok(()) 77 | } 78 | 79 | fn check_item_struct(item_struct: &ItemStruct) -> syn::Result<()> { 80 | // Because we wrap a struct name around the inner part of the macro 81 | // the struct shouldn't contain any attributes, generics, visibility 82 | assert_eq!(item_struct.attrs.len(), 0); 83 | assert_eq!(item_struct.generics.params.len(), 0); 84 | assert!(!Self::has_vis(&item_struct.vis)); 85 | for field in &item_struct.fields { 86 | if field.ident.is_none() { 87 | return Err(Error::new(field.span(), "Structx fields must have names!")); 88 | } 89 | Self::check_attrs(field.span(), &field.attrs)?; 90 | if Self::field_has_vis(field) { 91 | return Err(Error::new( 92 | field.span(), 93 | "Structx fields can't contain visibility modifiers!", 94 | )); 95 | } 96 | } 97 | Ok(()) 98 | } 99 | 100 | fn check_expr_struct(expr_struct: &ExprStruct) -> syn::Result<()> { 101 | // Because we wrap a struct name around the inner part of the macro 102 | // the struct shouldn't contain any attributes, Self type in path, 103 | // generics in path, leading colon 104 | assert_eq!(expr_struct.attrs.len(), 0); 105 | assert!(expr_struct.qself.is_none()); 106 | assert!(expr_struct.path.leading_colon.is_none()); 107 | assert!(expr_struct 108 | .path 109 | .segments 110 | .iter() 111 | .all(|s| s.arguments.is_none())); 112 | for field in expr_struct.fields.iter() { 113 | Self::check_named(field.span(), &field.member)?; 114 | Self::check_attrs(field.span(), &field.attrs)?; 115 | } 116 | Ok(()) 117 | } 118 | 119 | fn check_pat_struct(pat_struct: &PatStruct) -> syn::Result<()> { 120 | assert_eq!(pat_struct.attrs.len(), 0); 121 | assert!(pat_struct.qself.is_none()); 122 | assert!(pat_struct.path.leading_colon.is_none()); 123 | assert!(pat_struct 124 | .path 125 | .segments 126 | .iter() 127 | .all(|s| s.arguments.is_none())); 128 | for field in pat_struct.fields.iter() { 129 | Self::check_named(field.span(), &field.member)?; 130 | Self::check_attrs(field.span(), &field.attrs)?; 131 | } 132 | Ok(()) 133 | } 134 | 135 | fn parse_any(input: TokenStream) -> syn::Result { 136 | let wrapped_type_input = wrap_struct_name(Self::STRUCT_NAME, input.clone(), true); 137 | if let Ok(item_struct) = syn::parse2::(wrapped_type_input) { 138 | Self::check_item_struct(&item_struct)?; 139 | return Ok(StructX::Item(item_struct)); 140 | } 141 | let wrapped_input = wrap_struct_name(Self::STRUCT_NAME, input, false); 142 | if let Ok(expr_struct) = syn::parse2::(wrapped_input.clone()) { 143 | Self::check_expr_struct(&expr_struct)?; 144 | return Ok(StructX::Expr(expr_struct)); 145 | } 146 | let pat_struct_x = syn::parse2::(wrapped_input)?; 147 | Self::check_pat_struct(&pat_struct_x.0)?; 148 | Ok(StructX::Pattern(pat_struct_x.0)) 149 | } 150 | 151 | fn calc_fields(&self) -> Vec<(Ident, FieldValue)> { 152 | match self { 153 | StructX::Expr(expr_struct) => expr_struct 154 | .fields 155 | .iter() 156 | .map(|f| { 157 | ( 158 | named_member_ident(&f.member), 159 | FieldValue::Expr(f.expr.clone()), 160 | ) 161 | }) 162 | .collect(), 163 | StructX::Item(item_structs) => item_structs 164 | .fields 165 | .iter() 166 | .map(|f| (f.ident.clone().unwrap(), FieldValue::Type(f.ty.clone()))) 167 | .collect(), 168 | StructX::Pattern(pat_struct) => pat_struct 169 | .fields 170 | .iter() 171 | .map(|f| { 172 | ( 173 | named_member_ident(&f.member), 174 | FieldValue::Pat((*f.pat).clone()), 175 | ) 176 | }) 177 | .collect(), 178 | } 179 | } 180 | } 181 | 182 | #[inline] 183 | fn named_member_ident(member: &Member) -> Ident { 184 | match member { 185 | Member::Named(ident) => ident.clone(), 186 | Member::Unnamed(_) => panic!("Tried to access unnamed member as named member!"), 187 | } 188 | } 189 | 190 | fn wrap_struct_name( 191 | struct_name: &str, 192 | input: TokenStream, 193 | add_struct_keyword: bool, 194 | ) -> TokenStream { 195 | static STRUCT: &'static str = "struct"; 196 | let mut ts = TokenStream::new(); 197 | if add_struct_keyword { 198 | ts.extend(Ident::new(STRUCT, Span::call_site()).into_token_stream()); 199 | } 200 | ts.extend(Ident::new(struct_name, Span::call_site()).into_token_stream()); 201 | ts.extend(Some(TokenTree::Group(Group::new(Delimiter::Brace, input)))); 202 | ts 203 | } 204 | 205 | fn join_fields(fields: impl Iterator) -> (String, Vec) { 206 | static STRUCT_PREFIX: &'static str = "structx"; 207 | let mut fields = fields.collect::>(); 208 | fields.sort_by_key(|field| field.clone()); 209 | fields.into_iter().fold( 210 | (STRUCT_PREFIX.to_owned(), Vec::new()), 211 | |(mut struct_name, mut field_idents), ident| { 212 | let field_name = ident.to_string(); 213 | struct_name.push('_'); 214 | struct_name.push_str(&field_name.replace("_", "__")); 215 | field_idents.push(ident); 216 | (struct_name, field_idents) 217 | }, 218 | ) 219 | } 220 | 221 | type StructMap = HashMap>; 222 | 223 | struct StructxCollector<'a>(&'a mut StructMap); 224 | 225 | impl<'a> Visit<'_> for StructxCollector<'a> { 226 | fn visit_item_fn(&mut self, item_fn: &ItemFn) { 227 | visit::visit_item_fn(self, item_fn); 228 | 229 | for attr in &item_fn.attrs { 230 | if let Meta::Path(path) = &attr.meta { 231 | if path.leading_colon.is_none() && path.segments.len() == 1 { 232 | if path.segments.first().unwrap().ident == "named_args" { 233 | let fn_args = item_fn.sig.inputs.iter(); 234 | let mut idents = Vec::with_capacity(fn_args.len()); 235 | let mut types = Vec::with_capacity(fn_args.len()); 236 | for fn_arg in fn_args { 237 | match fn_arg { 238 | FnArg::Receiver(_) => (), 239 | FnArg::Typed(pat_type) => { 240 | if let Pat::Ident(pat_ident) = &*pat_type.pat { 241 | idents.push(pat_ident.ident.clone()); 242 | types.push((*pat_type.ty).clone()); 243 | } else { 244 | panic!("#[named_args] function's arguments should be either receiver or `id: Type`."); 245 | } 246 | } 247 | } 248 | } 249 | self.add_structx_definition(join_fields(idents.into_iter())); 250 | return; 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | fn visit_macro(&mut self, mac: &Macro) { 258 | visit::visit_macro(self, mac); 259 | self.collect_structx_in_macro(mac); 260 | } 261 | } 262 | 263 | impl<'a> StructxCollector<'a> { 264 | fn collect_structx_in_macro(&mut self, mac: &Macro) { 265 | static TYPE_MACRO_STR: &'static str = "Structx"; 266 | static MACRO_STR: &'static str = "structx"; 267 | // TODO support full qualified paths to structx: e.g: structx::structx { ... } 268 | if mac.path.leading_colon.is_none() && mac.path.segments.len() == 1 { 269 | let seg = mac.path.segments.first().unwrap(); 270 | if (seg.ident == MACRO_STR || seg.ident == TYPE_MACRO_STR) && seg.arguments.is_none() { 271 | self.parse_structx(mac.tokens.clone().into()); 272 | return; 273 | } 274 | } 275 | self.collect_structx_in_ts(mac.tokens.clone()); 276 | } 277 | fn collect_structx_in_ts(&mut self, input: TokenStream) { 278 | let mut tokens = input.into_iter(); 279 | while let Some(tt) = tokens.next() { 280 | match tt { 281 | TokenTree::Ident(ident) => { 282 | let name = ident.to_string(); 283 | if name == "structx" || name == "Structx" { 284 | if let Some(tt) = tokens.next() { 285 | if let TokenTree::Punct(punct) = tt { 286 | if punct.as_char() == '!' { 287 | if let Some(tt) = tokens.next() { 288 | if let TokenTree::Group(group) = tt { 289 | let inner = group.clone().stream(); 290 | self.collect_structx_in_ts(inner); 291 | self.parse_structx(group.stream()); 292 | } 293 | } 294 | } 295 | } 296 | } 297 | } 298 | } 299 | TokenTree::Group(group) => self.collect_structx_in_ts(group.stream()), 300 | _ => {} 301 | } 302 | } 303 | } 304 | // parse `structx!{}`/`Structx!{}`/`args!{}` in source files. 305 | fn parse_structx(&mut self, input: TokenStream) { 306 | // Moved parsing logic into StructX 307 | // StructX::parse_any tries to wrap the inner part of the macro into 308 | // struct StructX { #inner } and StructX { #inner } and tries to parse it as 309 | // ItemStruct, ExprStruct and PatStruct 310 | let struct_x = StructX::parse_any(input).unwrap(); // Throw error if parsing fails 311 | // Get the field-names + field-values from the parsed struct 312 | let (fields, values): (Vec, Vec) = 313 | struct_x.calc_fields().into_iter().unzip(); 314 | // Look for nested structx macro invocations in every field value 315 | for value in values { 316 | match value { 317 | FieldValue::Expr(expr) => { 318 | self.visit_expr(&expr); 319 | } 320 | FieldValue::Pat(pat) => { 321 | self.visit_pat(&pat); 322 | } 323 | FieldValue::Type(ty) => { 324 | self.visit_type(&ty); 325 | } 326 | } 327 | } 328 | // Add the struct_name and field_names to the struct map 329 | let joined_fields = join_fields(fields.into_iter()); 330 | self.add_structx_definition(joined_fields); 331 | } 332 | 333 | fn add_structx_definition(&mut self, (struct_name, field_idents): (String, Vec)) { 334 | self.0.entry(struct_name).or_insert(field_idents); 335 | } 336 | } 337 | 338 | fn main() { 339 | let mut struct_map = StructMap::new(); 340 | let mut structx_collector = StructxCollector(&mut struct_map); 341 | 342 | inwelling::collect_downstream(inwelling::Opts { 343 | watch_manifest: true, 344 | watch_rs_files: true, 345 | dump_rs_paths: true, 346 | }) 347 | .packages 348 | .into_iter() 349 | .for_each(|package| { 350 | package.rs_paths.unwrap().into_iter().for_each(|rs_path| { 351 | let contents = String::from_utf8(fs::read(rs_path.clone()).unwrap()).unwrap(); 352 | //let token_stream = contents.parse::().unwrap(); 353 | //structx_collector.collect_structx_in_ts(token_stream); 354 | let syntax = syn::parse_file(&contents); 355 | if let Ok(syntax) = syntax { 356 | structx_collector.visit_file(&syntax); 357 | } // it's better to report compile errors in downstream crates 358 | }) 359 | }); 360 | 361 | let (lens_traits, optic) = if cfg!(feature = "lens-rs") { 362 | ("#[derive( lens_rs::Lens )]", "#[optic] ") 363 | } else { 364 | ("", "") 365 | }; 366 | 367 | let output = struct_map 368 | .into_iter() 369 | .fold(String::new(), |acc, (struct_name, field_idents)| { 370 | format!( 371 | r#"{} 372 | #[allow( non_camel_case_types )] 373 | {lens_traits} 374 | #[derive( Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash )] 375 | pub struct {struct_name}<{generics}>{{{fields} 376 | }} 377 | "#, 378 | acc, 379 | lens_traits = lens_traits, 380 | struct_name = struct_name, 381 | generics = (1..field_idents.len()) 382 | .fold("T0".to_owned(), |acc, nth| format!("{},T{}", acc, nth)), 383 | fields = field_idents.iter().enumerate().fold( 384 | String::new(), 385 | |acc, (nth, field)| format!("{}\n {}pub {}: T{},", acc, optic, field, nth) 386 | ), 387 | ) 388 | }); 389 | 390 | let out_path = PathBuf::from(env::var("OUT_DIR").expect("$OUT_DIR should exist.")); 391 | fs::write(out_path.join("bindings.rs"), output).expect("bindings.rs generated."); 392 | } 393 | -------------------------------------------------------------------------------- /structx/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! See [RFC as Readme-For-Crate](https://github.com/oooutlk/structx/blob/main/README.md) 2 | //! for more. 3 | //! 4 | //! # Overview 5 | //! 6 | //! This project provides simulation of anonymous struct and named arguments in 7 | //! Rust, using proc macros `structx!{}`, `Structx!{}`, `#[named_args]` and 8 | //! `args!{}`. 9 | //! 10 | //! ## Usage of this crate 11 | //! 12 | //! Add the following in your Cargo.toml file: 13 | //! 14 | //! ```toml 15 | //! [dependencies] 16 | //! structx = "0.1" 17 | //! 18 | //! [build-dependencies] 19 | //! inwelling = "0.4" 20 | //! 21 | //! [package.metadata.inwelling] 22 | //! structx = true 23 | //! ``` 24 | //! 25 | //! Add the following in your build.rs file: 26 | //! 27 | //! ```rust 28 | //! inwelling::to( "structx" ); 29 | //! ``` 30 | //! Add the following in your .rs files: 31 | //! 32 | //! ```rust,no_run 33 | //! use structx::*; 34 | //! ``` 35 | //! 36 | //! If you want to use named arguments, add the following: 37 | //! 38 | //! ```rust,no_run 39 | //! use structx::named_args::*; 40 | //! ``` 41 | //! 42 | //! # Definitions and notations of anonymous structs 43 | //! 44 | //! Anonymous structs are `struct`s without the needs of providing struct names. 45 | //! However, the field names are mandatory. Anonymous structs are of the same type 46 | //! if and only if they are composed of the same set of field names. The order of 47 | //! fields are irrelevant. 48 | //! 49 | //! ## Value of anonymous structs 50 | //! 51 | //! The notation of an anonymous struct's value is `structx!{}`. 52 | //! 53 | //! ## Examples of anonymous struct's values 54 | //! 55 | //! ```rust,no_run 56 | //! let foo = structx!{ i: 3, b: true }; 57 | //! let bar = structx!{ x, y }; 58 | //! ``` 59 | //! 60 | //! ## Type of anonymous structs 61 | //! 62 | //! The notation of an anonymous struct's type is `Structx!{}`. 63 | //! 64 | //! ## Examples of anonymous struct's types 65 | //! 66 | //! ```rust,no_run 67 | //! fn foo( x: i32, y: i32 ) -> Structx!{ x: i32, y: i32 } { 68 | //! structx!{ x, y: y+1 } 69 | //! } 70 | //! ``` 71 | //! ## Traits derived for anonymous structs 72 | //! 73 | //! Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash. 74 | //! 75 | //! ```rust 76 | //! let a = structx!{ width : 800, height: 600 }; 77 | //! let b = structx!{ height: 600, width : 800 }; 78 | //! let c = structx!{ width : 1024, height: 768 }; 79 | //! assert_eq!( a, b ); 80 | //! assert_ne!( a, c ); 81 | //! ``` 82 | //! 83 | //! # Simulation of named arguments 84 | //! 85 | //! At definition site, add attributes `#[named_args]` to functions. 86 | //! 87 | //! ```rust,no_run 88 | //! #[named_args] 89 | //! fn set_size( width: u32, height: u32 ) { todo!() } 90 | //! ``` 91 | //! 92 | //! At call site, wrap arguments with `args!{}`. 93 | //! 94 | //! ```rust,no_run 95 | //! set_size( args!{ width: 1024, height: 768 }); 96 | //! ``` 97 | //! 98 | //! # License 99 | //! 100 | //! Under Apache License 2.0 or MIT License, at your will. 101 | 102 | pub use structx_derive::{structx, Structx}; 103 | 104 | /// Simulating named arguments. 105 | /// `#[named_args]` is for functions with named arguments. 106 | /// `arg!{}` is an alias for `structx!{}`. 107 | pub mod named_args { 108 | pub use structx_derive::{named_args, structx as args}; 109 | } 110 | 111 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 112 | -------------------------------------------------------------------------------- /structx_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structx_derive" 3 | version = "0.1.4" 4 | authors = ["oooutlk "] 5 | edition = "2021" 6 | license = "MIT/Apache-2.0" 7 | keywords = [ "struct", "anonymous", "named", "arguments", "lens" ] 8 | readme = "README.md" 9 | repository = "https://github.com/oooutlk/structx" 10 | documentation = "https://docs.rs/structx_derive" 11 | categories = [ "rust-patterns" ] 12 | description = "Macros for anonymous struct and named arguments." 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0" 19 | quote = "1.0" 20 | syn = { version = "2.0", features = ["extra-traits","full","visit","visit-mut"] } 21 | -------------------------------------------------------------------------------- /structx_derive/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This crate provides proc macros for simulating anonymous struct and named 4 | arguments in Rust: 5 | 6 | - `structx!{}` for denoting values of anonymous structs. 7 | 8 | - `Structx!{}` for denoting types of anonymous structs. 9 | 10 | - `#[named_args]` for denoting functions with named arguments. 11 | 12 | - `args!{}` for denoting named arguments. 13 | 14 | See crate `structx`'s README.md for more. 15 | 16 | # License 17 | 18 | Under Apache License 2.0 or MIT License, at your will. 19 | -------------------------------------------------------------------------------- /structx_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::{Delimiter, Group, TokenStream, TokenTree}; 3 | 4 | extern crate proc_macro2; 5 | use proc_macro2::Span; 6 | 7 | use quote::{quote, ToTokens}; 8 | 9 | use std::mem; 10 | 11 | use syn::spanned::Spanned; 12 | use syn::{ 13 | parse::{Parse, ParseStream}, 14 | parse_macro_input, parse_quote, 15 | punctuated::Punctuated, 16 | token::Colon, 17 | visit_mut::{self, VisitMut}, 18 | ExprStruct, FnArg, Ident, ItemFn, Member, Pat, PatStruct, Token, Type, 19 | }; 20 | 21 | /* 22 | * Introduced a new type which can be parsed with syn::parse2. 23 | * This is necessary because syn version 2 doesn't implement Parse for Pat 24 | */ 25 | struct StructX { 26 | pat: PatStruct, 27 | } 28 | 29 | impl Parse for StructX { 30 | fn parse(input: ParseStream) -> syn::Result { 31 | let pat = Pat::parse_single(input)?; 32 | if let Pat::Struct(pat) = pat { 33 | Ok(StructX { pat }) 34 | } else { 35 | Err(syn::Error::new( 36 | pat.span(), 37 | "structx!()'s supported pattern matching is struct only.", 38 | )) 39 | } 40 | } 41 | } 42 | 43 | /// Value of anonymous struct. 44 | #[proc_macro] 45 | pub fn structx(input: TokenStream) -> TokenStream { 46 | let input_expr = wrap_struct_name("structx_", input.clone()); 47 | if let Ok(expr_struct) = syn::parse::(input_expr) { 48 | if let Some(rest) = &expr_struct.rest { 49 | let rest = &**rest; 50 | let specified_idents = expr_struct.fields.iter().map(|field| { 51 | if let Member::Named(field_ident) = &field.member { 52 | field_ident 53 | } else { 54 | panic!(); 55 | } 56 | }); 57 | let specified_exprs = expr_struct.fields.iter().map(|field| &field.expr); 58 | return quote!({ let mut _rest = #rest; #( _rest.#specified_idents = #specified_exprs; )* _rest }).into(); 59 | } else { 60 | let struct_name = get_struct_name(expr_struct.fields.iter().map(|field| &field.member)); 61 | return wrap_struct_name(&struct_name, input); 62 | } 63 | } else { 64 | let input_pat = wrap_struct_name("structx_", input.clone()); 65 | if let Ok(struct_x) = syn::parse::(input_pat) { 66 | let struct_name = 67 | get_struct_name(struct_x.pat.fields.iter().map(|field| &field.member)); 68 | return wrap_struct_name(&struct_name, input); 69 | } 70 | } 71 | panic!("structx!() should be some struct."); 72 | } 73 | 74 | struct StructxField { 75 | ident: Ident, 76 | _colon: Colon, 77 | ty: Type, 78 | } 79 | 80 | impl Parse for StructxField { 81 | fn parse(input: ParseStream) -> syn::Result { 82 | Ok(StructxField { 83 | ident: input.parse()?, 84 | _colon: input.parse()?, 85 | ty: input.parse()?, 86 | }) 87 | } 88 | } 89 | 90 | struct StructxType(Punctuated); 91 | 92 | impl Parse for StructxType { 93 | fn parse(input: ParseStream) -> syn::Result { 94 | Ok(StructxType( 95 | Punctuated::::parse_terminated(input)?, 96 | )) 97 | } 98 | } 99 | 100 | /// Type of anonymous struct. 101 | #[allow(non_snake_case)] 102 | #[proc_macro] 103 | pub fn Structx(input: TokenStream) -> TokenStream { 104 | let structx_type = syn::parse::(input).expect( 105 | "`Structx!{}` should be in the form of `Structx!{ field0: Type0, field1: Type1, .. }`.", 106 | ); 107 | let (struct_name, _, field_types) = join_fields( 108 | structx_type 109 | .0 110 | .into_iter() 111 | .map(|structx_field| (structx_field.ident, Some(structx_field.ty))), 112 | ); 113 | 114 | let struct_ident = Ident::new(&struct_name, Span::call_site()); 115 | quote!( #struct_ident<#( #field_types),*> ).into() 116 | } 117 | 118 | fn wrap_struct_name(struct_name: &str, input: TokenStream) -> TokenStream { 119 | let mut ts = TokenStream::from(Ident::new(struct_name, Span::call_site()).into_token_stream()); 120 | ts.extend(Some(TokenTree::Group(Group::new(Delimiter::Brace, input)))); 121 | ts 122 | } 123 | 124 | fn join_fields( 125 | fields: impl Iterator)>, 126 | ) -> (String, Vec, Vec>) { 127 | let mut fields = fields.collect::>(); 128 | fields.sort_by_key(|field| field.0.clone()); 129 | fields.into_iter().fold( 130 | ("structx".to_owned(), Vec::new(), Vec::new()), 131 | |(mut struct_name, mut field_idents, mut field_types), (ident, ty)| { 132 | let field_name = ident.to_string(); 133 | struct_name.push('_'); 134 | struct_name.push_str(&field_name.replace("_", "__")); 135 | field_idents.push(ident.clone()); 136 | field_types.push(ty); 137 | (struct_name, field_idents, field_types) 138 | }, 139 | ) 140 | } 141 | 142 | fn get_struct_name<'a>(members: impl Iterator) -> String { 143 | join_fields( 144 | members 145 | .map(|member| { 146 | if let Member::Named(ident) = member { 147 | ident.clone() 148 | } else { 149 | panic!("structx!()'s fields should have names."); 150 | } 151 | }) 152 | .zip((0..).map(|_| None)), 153 | ) 154 | .0 155 | } 156 | 157 | struct FnWithNamedArg; 158 | 159 | impl VisitMut for FnWithNamedArg { 160 | fn visit_item_fn_mut(&mut self, item_fn: &mut ItemFn) { 161 | visit_mut::visit_item_fn_mut(self, item_fn); 162 | 163 | let inputs = mem::take(&mut item_fn.sig.inputs); 164 | let fn_args = inputs.into_iter(); 165 | let mut idents = Vec::with_capacity(fn_args.len()); 166 | let mut types = Vec::with_capacity(fn_args.len()); 167 | 168 | for fn_arg in fn_args { 169 | match fn_arg { 170 | FnArg::Receiver(_) => item_fn.sig.inputs.push(fn_arg), 171 | FnArg::Typed(pat_type) => { 172 | if let Pat::Ident(pat_ident) = &*pat_type.pat { 173 | idents.push(pat_ident.ident.clone()); 174 | types.push(pat_type.ty); 175 | } else { 176 | panic!("#[named_args] function's arguments should be either receiver or `id: Type`."); 177 | } 178 | } 179 | } 180 | } 181 | let (struct_name, field_idents, field_types) = join_fields( 182 | idents 183 | .into_iter() 184 | .zip(types.into_iter().map(|ty| Some(*ty))), 185 | ); 186 | let struct_ident = Ident::new(&struct_name, Span::call_site()); 187 | 188 | item_fn.sig.inputs.push(parse_quote!( 189 | #struct_ident { 190 | #( #field_idents ),* 191 | } : #struct_ident< #(#field_types),*> 192 | )); 193 | } 194 | } 195 | 196 | /// Simulation of named arguments. 197 | #[proc_macro_attribute] 198 | pub fn named_args(_args: TokenStream, input: TokenStream) -> TokenStream { 199 | let mut item_fn = parse_macro_input!(input as ItemFn); 200 | FnWithNamedArg.visit_item_fn_mut(&mut item_fn); 201 | quote!( #item_fn ).into() 202 | } 203 | -------------------------------------------------------------------------------- /structx_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structx_test" 3 | version = "0.1.10" 4 | authors = ["oooutlk "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | structx = { path = "../structx", version = "0.1.10" } 10 | lens-rs = { version = "0.3", features = ["structx"], optional = true } 11 | 12 | [build-dependencies] 13 | inwelling = "0.5" 14 | 15 | [package.metadata.inwelling] 16 | structx = true 17 | lens-rs_generator = true 18 | 19 | [features] 20 | lens = [ "lens-rs", "structx/lens-rs" ] 21 | -------------------------------------------------------------------------------- /structx_test/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | inwelling::to("structx"); 3 | if std::env::var("CARGO_FEATURE_LIBTK").is_ok() { 4 | inwelling::to("lens-rs"); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /structx_test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use std::collections::HashMap; 4 | use structx::*; 5 | 6 | #[test] 7 | fn anonymous_struct() { 8 | let a = structx! { width : 800, height: 600 }; 9 | let b = structx! { height: 600, width : 800 }; 10 | let c = structx! { width : 1024, height: 768 }; 11 | assert_eq!(a, b); 12 | assert_ne!(a, c); 13 | } 14 | 15 | #[test] 16 | fn returns_anonymous_struct() { 17 | fn returns_structx(x: i32, y: i32) -> Structx! { x: i32, y: i32 } { 18 | structx! { x, y } 19 | } 20 | 21 | assert_eq!(returns_structx(3, 4), structx! { x:3, y:4 }); 22 | 23 | #[derive(Debug, PartialEq)] 24 | struct Bar(T); 25 | 26 | fn returns_generic_structx(bar: Bar) -> Structx! { bar: Bar, baz: bool } { 27 | structx! { bar, baz: true } 28 | } 29 | 30 | assert_eq!( 31 | returns_generic_structx(Bar("bar")), 32 | structx! { bar: Bar("bar"), baz: true } 33 | ); 34 | } 35 | 36 | #[test] 37 | fn nested_anonymous_struct() { 38 | let pixel = structx! { 39 | color: structx!{ red: 255u8, green: 0u8, blue: 0u8 }, 40 | position: structx!{ x: 3u16, y: 4u16 }, 41 | }; 42 | 43 | fn returns_nested_structx( 44 | red: u8, 45 | green: u8, 46 | blue: u8, 47 | x: u16, 48 | y: u16, 49 | ) -> Structx! { 50 | color: Structx!{ red: u8, green: u8, blue: u8 }, 51 | position: Structx!{ x: u16, y: u16 }, 52 | } { 53 | structx! { 54 | color: structx!{ red, green, blue }, 55 | position: structx!{ x, y }, 56 | } 57 | } 58 | 59 | assert_eq!(returns_nested_structx(255, 0, 0, 3, 4), pixel); 60 | 61 | let recursive_structx = structx! { a: structx!{ a: "recursive structx" }, }; 62 | 63 | fn returns_recursive_structx() -> Structx! { a: Structx!{ a: &'static str }} { 64 | structx! { a: structx!{ a: "recursive structx" }} 65 | } 66 | 67 | assert_eq!(returns_recursive_structx(), recursive_structx); 68 | } 69 | 70 | #[test] 71 | fn structx_inside_other_macros() { 72 | let _vs = vec![structx! { lelele: 0 }]; 73 | let _vsvs = vec![structx! { lalala: vec![structx!{ lelele: 0 }]}]; 74 | } 75 | 76 | #[test] 77 | fn type_only_anonymous_struct() { 78 | #[allow(dead_code)] 79 | type Props = Structx! { 80 | items: Vec, 81 | products: Vec 82 | }; 83 | } 84 | 85 | #[test] 86 | fn recursively_nested_anonymous_struct() { 87 | let model = structx! { 88 | products: vec![ 89 | structx! { 90 | id: 0, 91 | name: "Pullover" 92 | }, 93 | structx! { 94 | id: 1, 95 | name: "T-Shirt" 96 | }, 97 | ], 98 | session: structx! { 99 | user: structx! { 100 | username: "xxtheusernamexx" 101 | }, 102 | tokens: HashMap::from([("a0bd6d46-3324-4566-836b-96b3767b6295", structx! { 103 | valid_until: 1684249334, 104 | created: 1664249334 105 | })]) 106 | } 107 | }; 108 | 109 | fn returns_nested_structx( 110 | username: &str, 111 | ) -> Structx! { 112 | products: Vec, 113 | session: Structx! { 114 | user: Structx! { username: &str }, 115 | tokens: HashMap<&str, Structx!{ valid_until: usize, created: usize }> } 116 | } { 117 | structx! { 118 | products: vec![ 119 | structx! { 120 | id: 0, 121 | name: "Pullover" 122 | }, 123 | structx! { 124 | id: 1, 125 | name: "T-Shirt" 126 | }, 127 | ], 128 | session: structx! { 129 | user: structx! { 130 | username 131 | }, 132 | tokens: HashMap::from([("a0bd6d46-3324-4566-836b-96b3767b6295", structx! { 133 | valid_until: 1684249334, 134 | created: 1664249334 135 | })]) 136 | } 137 | } 138 | } 139 | assert_eq!(returns_nested_structx("xxtheusernamexx"), model); 140 | } 141 | 142 | #[test] 143 | fn named_argsuments() { 144 | use structx::named_args::*; 145 | 146 | #[named_args] 147 | fn with_owned_args(x: i32, y: String) -> String { 148 | format!("{} {}", x, y) 149 | } 150 | 151 | #[named_args] 152 | fn with_borrowed_args<'a>(x: bool, y: &'a str) -> String { 153 | format!("{} {}", x, y) 154 | } 155 | 156 | assert_eq!( 157 | with_owned_args(args! { x: 3, y: "4".to_owned() }), 158 | "3 4".to_owned() 159 | ); 160 | assert_eq!( 161 | with_borrowed_args(args! { x: true, y: "false" }), 162 | "true false".to_owned() 163 | ); 164 | } 165 | 166 | #[test] 167 | fn test_pattern_matching() { 168 | let alpha = 42u8; 169 | let beta = true; 170 | let my_record = structx! { 171 | alpha, 172 | beta , 173 | gamma: "Dancing Ferris", 174 | }; 175 | match my_record { 176 | structx! { alpha, beta, gamma } => println!("{}, {}, {}", alpha, beta, gamma), 177 | } 178 | 179 | let structx! { alpha, beta, gamma } = my_record; 180 | println!("{}, {}, {}", alpha, beta, gamma); 181 | } 182 | 183 | #[test] 184 | fn test_struct_update_syntax() { 185 | let yellow = structx! { red: 0, green: 255, blue: 255 }; 186 | let white = structx! { red: 255, ..yellow }; 187 | assert_eq!(white, structx! { red: 255, green: 255, blue: 255 }); 188 | } 189 | 190 | #[cfg(feature = "lens")] 191 | #[test] 192 | fn lens_test_nested() { 193 | use lens_rs::*; 194 | 195 | #[derive(Copy, Clone, Debug, Review, Prism)] 196 | enum Either { 197 | #[optic] 198 | Left(L), 199 | #[optic] 200 | Right(R), 201 | } 202 | use Either::*; 203 | 204 | #[derive(Copy, Clone, Debug, Lens)] 205 | struct Tuple(#[optic] A, #[optic] B); 206 | 207 | let mut x: ( 208 | i32, 209 | Either>, i32>, i32>, 210 | ) = ( 211 | 1, 212 | Left(Tuple( 213 | vec![ 214 | Some(structx! { 215 | a : "a".to_string(), 216 | b : 2, 217 | }), 218 | None, 219 | Some(structx! { 220 | a : 'c'.to_string(), 221 | b : 3, 222 | }), 223 | ], 224 | 4, 225 | )), 226 | ); 227 | 228 | x.preview_mut(optics!(_1.Left._1)).map(|x| *x *= 2); 229 | assert_eq!(x.preview_ref(optics!(_1.Left._1)), Some(&8)); 230 | 231 | x.preview_mut(optics!(_1.Right)).map(|x: &mut i32| *x *= 2); 232 | assert_eq!(x.preview_ref(optics!(_1.Right)), None); 233 | 234 | *x.view_mut(optics!(_0)) += 1; 235 | assert_eq!(x.0, 2); 236 | 237 | x.traverse_mut(optics!(_1.Left._0._mapped.Some.a)) 238 | .into_iter() 239 | .for_each(|s| *s = s.to_uppercase()); 240 | assert_eq!( 241 | x.traverse(optics!(_1.Left._0._mapped.Some.a)), 242 | vec!["A".to_string(), "C".to_string()] 243 | ); 244 | } 245 | } 246 | --------------------------------------------------------------------------------