├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── basic_cascades.rs └── src └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run examples 15 | run: cargo run --example basic_cascades 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | /.idea/ 6 | *.iml -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cascade" 3 | version = "1.0.1" 4 | authors = ["Jane Lewis "] 5 | description = "Dart-like cascade macro for Rust" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/inquisitivepenguin/cascade" 9 | 10 | [dependencies] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Jane Lewis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `cascade`: Cascade expressions in Rust! 2 | `cascade` is a macro library for Rust that makes it easy and ergonomic 3 | to use cascade-like expressions, similar to Dart. 4 | 5 | ```rust 6 | #[macro_use] 7 | extern crate cascade; 8 | 9 | fn main() { 10 | let cascaded_list = cascade! { 11 | Vec::new(); 12 | ..push("Cascades"); 13 | ..push("reduce"); 14 | ..push("boilerplate"); 15 | }; 16 | println!("{:?}", cascaded_list); // Will print '["Cascades", "reduce", "boilerplate"]' 17 | } 18 | ``` 19 | 20 | This is only a small example of what `cascade` lets you do: 21 | the `basic_cascades` example in this repository covers the other 22 | cool features of the `cascade!` macro. 23 | 24 | #### Why does this need to exist? 25 | Cascades reduce boilerplate by eliminating the need for a 'temporary' 26 | variable when making several method calls in a row, and it also 27 | helps make struct member assignments look more ergonomic. For 28 | example: 29 | ```rust 30 | #[macro_use] 31 | extern crate cascade; 32 | 33 | #[derive(Clone, Debug)] 34 | struct Person { 35 | pub name: String, 36 | pub age: u32, 37 | pub height: u32, 38 | pub friend_names: Vec 39 | } 40 | 41 | fn main() { 42 | // Without cascades 43 | let person = Person { 44 | name: "John Smith", 45 | age: 17, 46 | height: 68, // 5' 8" 47 | friend_names: { 48 | let mut tmp_names = Vec::new(); 49 | tmp_names.push("James Smith".to_string()); 50 | tmp_names.push("Bob Jones".to_string()); 51 | tmp_names.push("Billy Jones".to_string()); 52 | tmp_names 53 | } 54 | }; 55 | // With cascades 56 | let person = Person { 57 | name: "John Smith", 58 | age: 17, 59 | height: 68, 60 | friend_names: cascade! { 61 | Vec::new(); 62 | ..push("James Smith".to_string()); 63 | ..push("Bob Jones".to_string()); 64 | ..push("Billy Jones".to_string()); 65 | } 66 | }; 67 | // Cascades also let you do cool stuff like this 68 | let person_one_year_later = cascade! { 69 | person; 70 | ..age += 1; 71 | ..height += 2; 72 | }; 73 | } 74 | ``` 75 | 76 | In addition, cascades make it easier to design fluent interfaces. 77 | No more returning `self` with every single function! 78 | 79 | ### Changelog 80 | **1.0.0**: `cascade` has reached 1.0! Here are some of the cool new features and syntax 81 | changes made as a part of this: 82 | - The syntax for binding variables has been changed to use `let` syntax. This makes it more 83 | in-line with Rust syntax and also allows you to specify the type of a cascaded expression. 84 | ```rust 85 | cascade! { 86 | // If you don't need to bind the statement to an identifier, you can use _ 87 | let _: Vec = vec![1,2,3].into_iter().map(|x| x + 1).collect(); 88 | ..push(1); 89 | } 90 | ``` 91 | - Statements no longer need `|` in front of them. You can just put the statement right in there, no prefix needed. 92 | - You can return expressions from cascades, just like normal Rust blocks. By default, 93 | cascades will already return the value of the cascaded variable. But if you want to return 94 | a custom expression, you can put it at the end of a cascade block, with no semicolon. 95 | ```rust 96 | let has_more_than_three_elements = cascade! { 97 | let v = vec![1,2,3]; 98 | ..push(4); 99 | v.len() > 3 100 | }; 101 | println!("{}", cascade! { 102 | vec![1,2,3]; 103 | ..push(4); 104 | ..into_iter().fold(0, |acc, x| acc + x) 105 | }); 106 | ``` 107 | - Finally, you can have nested blocks within a cascade block. For example: 108 | ```rust 109 | cascade! { 110 | vec![1,2,3]; 111 | { 112 | let a = 1; 113 | ..push(a); 114 | }; 115 | } 116 | ``` 117 | 118 | I hope you enjoy cascade `1.0`! Remember to leave any complaints or suggestions 119 | on [the issue tracker](https://github.com/InquisitivePenguin/cascade/issues). 120 | 121 | **0.1.3**: The ? operator now works with cascades, for scenarios like this: 122 | ```rust 123 | fn file_read() -> Result { 124 | cascade! { 125 | SomeFileClass::create_file_reader("test.txt"); 126 | ..load()?; 127 | ..check_validity()?; 128 | } 129 | } 130 | ``` 131 | 132 | **0.1.2**: You can now chain methods together, like this: 133 | ```rust 134 | fn chain_example() { 135 | cascade! { 136 | FnChainTest::new(); 137 | ..chain().chain().chain(); 138 | } 139 | } 140 | ``` 141 | 142 | ### Credits 143 | Written by Jane Lewis 144 | -------------------------------------------------------------------------------- /examples/basic_cascades.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate cascade; 3 | 4 | #[derive(Clone, Debug)] 5 | struct Person { 6 | name: String, 7 | age: u32, 8 | height: u32, 9 | } 10 | 11 | impl Person { 12 | pub fn blank() -> Person { 13 | Person { 14 | name: "".to_string(), 15 | age: 0, 16 | height: 0, 17 | } 18 | } 19 | } 20 | 21 | #[derive(Clone, Debug)] 22 | struct Chain { 23 | links: Vec, 24 | } 25 | 26 | impl Chain { 27 | fn blank() -> Chain { 28 | Chain { links: vec![] } 29 | } 30 | fn add(mut self, link: u32) -> Self { 31 | self.links.push(link); 32 | self 33 | } 34 | } 35 | 36 | #[allow(unused)] 37 | fn main() { 38 | // Cascades can be used recursively! 39 | let people = cascade! { 40 | Vec::new(); 41 | ..push(cascade! { 42 | Person::blank(); 43 | ..name = "John Smith".to_string(); 44 | ..height = 72; // 6 feet, or 72 inches tall 45 | ..age = 34; 46 | }); 47 | // This is what an expanded cascade looks like. 48 | ..push({ 49 | let mut __tmp = Person::blank(); 50 | __tmp.name = "Jason Smith".to_string(); 51 | __tmp.height = 64; 52 | __tmp.age = 34; 53 | __tmp 54 | }); 55 | }; 56 | // Any expression works as the first statement of a cascade. 57 | let other_person = cascade! { 58 | people[0].clone(); 59 | ..name = "Bob Smith".to_string(); 60 | ..height = 66; 61 | }; 62 | // You can also use +=, -=, *=, /= for operators 63 | let another_person = cascade! { 64 | other_person.clone(); 65 | ..name = "Joan Smith".to_string(); 66 | ..age += 3; 67 | ..height -= 4; 68 | }; 69 | // You can put regular statements inside of a cascade macro 70 | let yet_another_person = cascade! { 71 | people[0].clone(); 72 | ..name = "James Smith".to_string(); 73 | ..age = 27; 74 | println!("Cascades in Rust are cool!"); 75 | ..height -= 3; 76 | }; 77 | // You can bind the initial value of the cascade to an identifier, which reflects the current state of the cascaded value. 78 | let one_more_person = cascade! { 79 | let person = people[0].clone(); 80 | println!("'person' was equal to: {:?}", person); 81 | ..name = "James Smith".to_string(); 82 | ..height = ((person.height as f32) * 0.8) as u32; 83 | println!("'person' is now equal to: {:?}", person); 84 | }; 85 | // As of version 0.1.2, you can also chain methods together. Observe: 86 | let method_chain_example = cascade! { 87 | let ch = Chain::blank(); 88 | ..add(5).add(6).add(7); // In this case, ch is consumed here. So we have to shadow ch to avoid an error. Obviously, this isn't the most useful way to method-chain. 89 | let ch = (); 90 | }; 91 | 92 | // You can have nested blocks within the cascade 93 | let block_example = cascade! { 94 | Vec::new(); 95 | ..push(1); 96 | ..push(2); 97 | }; 98 | 99 | let has_more_than_three_elements = cascade! { 100 | let v = vec![1,2,3]; 101 | ..push(4); 102 | v.len() > 3 103 | }; 104 | 105 | println!("{}", cascade! { 106 | vec![1,2,3]; 107 | ..push(4); 108 | ..into_iter().fold(0, |acc, x| acc + x) 109 | }); 110 | 111 | cascade! { 112 | let _: Vec = vec![1,2,3].into_iter().map(|x| x + 1).collect(); 113 | ..push(1); 114 | }; 115 | 116 | option_cascade_test().unwrap().unwrap(); 117 | } 118 | 119 | // As of version 0.1.3, you can use the ? operator after a .. statement. 120 | fn option_cascade_test() -> Result, ()> { 121 | let question_mark_operator_example: Result, ()> = cascade! { 122 | Ok(Ok(())); 123 | ..unwrap()?; 124 | }; 125 | return question_mark_operator_example; 126 | } 127 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | /// A macro for chaining together statements that act on an initial expression. 3 | /// # Usage 4 | /// Cascading allows you to run multiple statements on a struct generated by an expression without having 5 | /// to repeatedly type it out. This can be very convinient for modifying multiple properties simultaneously, 6 | /// or running a long series of individual methods on a struct. 7 | /// 8 | /// When writing a cascade, the first line specifies the expression that will be operated upon. 9 | /// All following lines modify the struct created by the expression in some way. 10 | /// The most common operator is the `..` member operator, which is borrowed from Dart's syntax. 11 | /// It's a convinient shorthand for accessing a member or method of the struct. For example: 12 | /// ``` 13 | /// use cascade::cascade; 14 | /// 15 | /// let old_vector = vec!(1, 2, 3); 16 | /// let new_vector = cascade! { 17 | /// old_vector; 18 | /// ..push(4); 19 | /// ..push(5); 20 | /// ..push(6); 21 | /// }; 22 | /// ``` 23 | /// 24 | /// Remember, you can't move the struct, because it gets returned at the end of the cascade. In other words, 25 | /// you can't run a method on a struct with the `..` operator if the method takes `self` or 26 | /// `mut self`. But `&self` or `&mut self` works fine. 27 | /// 28 | /// You can also put statements without a `..` in a cascade macro: 29 | /// ``` 30 | /// use cascade::cascade; 31 | /// use std::collections::HashMap; 32 | /// 33 | /// let hash_map = cascade! { 34 | /// HashMap::new(); 35 | /// ..insert("foo", "bar"); 36 | /// println!("Look! You can put statements in a cascade!"); 37 | /// for i in 0..3 { 38 | /// println!("You can put loops in here too! Make sure to put a semicolon at the end!"); 39 | /// }; 40 | /// }; 41 | /// ``` 42 | /// 43 | /// If you need to reference the expression inside a cascade, you can name it using `let`: 44 | /// ``` 45 | /// use cascade::cascade; 46 | /// 47 | /// let vector = cascade! { 48 | /// let v = Vec::new(); 49 | /// ..push(1); 50 | /// println!("The vector now has {} element(s).", v.len()); 51 | /// ..push(2); 52 | /// }; 53 | /// ``` 54 | /// This will print `The vector now has 1 element(s).` 55 | /// 56 | /// Once again, trying to move this value will throw an error. 57 | /// 58 | /// Finally, you can also use the member operator (`..`) to set or change members of the cascaded struct: 59 | /// ``` 60 | /// use cascade::cascade; 61 | /// struct A { 62 | /// pub b: u32, 63 | /// pub c: u32 64 | /// } 65 | /// 66 | /// let a = cascade! { 67 | /// A { b: 0, c: 0 }; 68 | /// ..b = 5; 69 | /// ..c += 1; 70 | /// }; 71 | /// ``` 72 | /// 73 | /// More examples of the cascade macro can be found in the examples folder on the Github repository. 74 | #[macro_export] 75 | macro_rules! cascade { 76 | (let _ : $t:ty = $e: expr; $($tail: tt)*) => { 77 | cascade!(let __tmp: $t = $e; $($tail)*) 78 | }; 79 | (let $i:ident : $t:ty = $e: expr; $($tail: tt)*) => { 80 | { 81 | let mut $i: $t = $e; 82 | cascade!(@line $i, $($tail)*) 83 | } 84 | }; 85 | (let $i:ident = $e: expr; $($tail: tt)*) => { 86 | { 87 | let mut $i = $e; 88 | cascade!(@line $i, $($tail)*) 89 | } 90 | }; 91 | ($e: expr; $($tail: tt)*) => { 92 | cascade!(let __tmp = $e; $($tail)*) 93 | }; 94 | (@line $i: ident, .. $v: ident = $e: expr; $($tail: tt)*) => { 95 | { 96 | $i.$v = $e; 97 | $crate::cascade!(@line $i, $($tail)*) 98 | } 99 | }; 100 | (@line $i:ident, .. $v:ident += $e:expr; $($tail:tt)*) => { 101 | { 102 | $i.$v += $e; 103 | $crate::cascade!(@line $i, $($tail)*) 104 | } 105 | }; 106 | (@line $i:ident, .. $v:ident -= $e:expr; $($tail:tt)*) => { 107 | { 108 | $i.$v -= $e; 109 | $crate::cascade!(@line $i, $($tail)*) 110 | } 111 | }; 112 | (@line $i:ident, .. $v:ident *= $e:expr; $($tail:tt)*) => { 113 | { 114 | $i.$v *= $e; 115 | $crate::cascade!(@line $i, $($tail)*) 116 | } 117 | }; 118 | (@line $i:ident, .. $($q: ident ($($e: expr),*)).+; $($tail: tt)*) => { 119 | { 120 | $i.$($q($($e),*)).+; 121 | $crate::cascade!(@line $i, $($tail)*) 122 | } 123 | }; 124 | (@line $i:ident, .. $($q: ident ($($e: expr),*)).+?; $($tail: tt)*) => { 125 | { 126 | $i.$($q($($e),*)).+?; 127 | $crate::cascade!(@line $i, $($tail)*) 128 | } 129 | }; 130 | (@line $i:ident, { $($t:tt)* }; $($tail: tt)*) => { 131 | { 132 | { $crate::cascade!(@line $i, $($t)*); } 133 | $crate::cascade!(@line $i, $($tail)*) 134 | } 135 | }; 136 | (@line $i:ident, $s: stmt; $($tail: tt)*) => { 137 | { 138 | $s 139 | cascade!(@line $i, $($tail)*) 140 | } 141 | }; 142 | (@line $i:ident, { $($t:tt)* }) => { 143 | { $crate::cascade!(@line $i, $($t)*) } 144 | }; 145 | (@line $i:ident, .. $($q: ident ($($e: expr),*)).+) => { 146 | $i.$($q($($e),*)).+ 147 | }; 148 | (@line $i:ident, $e:expr) => { 149 | $e 150 | }; 151 | (@line $i:ident,) => { 152 | $i 153 | }; 154 | () => {} 155 | } 156 | --------------------------------------------------------------------------------