assign/
lib.rs

1//! This module provides the `assign!` macro to allow mutating a struct value in
2//! a declarative style.
3//!
4//! It is an alternative to [struct update syntax][] that works with
5//! [non-exhaustive][] structs.
6//!
7//! [struct update syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax
8//! [non-exhaustive]: https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute
9//!
10//! It is used as
11//!
12//! ```
13//! # use assign::assign;
14//! # struct Struct { field: u8 }
15//! # let init_expression = Struct { field: 0 };
16//! # let new_field_value = 1;
17//! let foo = assign!(init_expression, {
18//!     field: new_field_value,
19//!     // other_field: new_other_field_value,
20//!     // ...
21//! });
22//! ```
23//!
24//! For details and examples, see the documentation for the macro itself.
25#![no_std]
26
27/// Mutate a struct value in a declarative style.
28///
29/// # Basic usage
30///
31/// ```
32/// # use assign::assign;
33/// #
34/// #[non_exhaustive]
35/// #[derive(Debug, PartialEq)]
36/// struct SomeStruct {
37///     a: u32,
38///     b: Option<f32>,
39///     c: String,
40/// }
41///
42/// impl SomeStruct {
43///     fn new() -> Self {
44///         // ...
45/// #       SomeStruct {
46/// #           a: 1u32,
47/// #           b: None,
48/// #           c: String::from("old"),
49/// #       }
50///     }
51/// }
52///
53/// let instance1 = assign!(SomeStruct::new(), {
54///     a: 2,
55///     c: "new".into(),
56/// });
57///
58/// // The same thing using mutation explicitly.
59/// // This is what the above expands to.
60/// let instance2 = {
61///     let mut item = SomeStruct::new();
62///     item.a = 2;
63///     item.c = "new".into();
64///     item
65/// };
66///
67/// // The same thing using struct update syntax (does not work for
68/// // non-exhaustive structs defined in external crates).
69/// let instance3 = SomeStruct {
70///     a: 2,
71///     c: "new".into(),
72///     ..SomeStruct::new()
73/// };
74///
75/// assert_eq!(instance1, instance2);
76/// assert_eq!(instance1, instance3);
77/// ```
78///
79/// # Slightly more realistic example
80///
81/// ```
82/// # struct Arg {}
83/// # impl Arg { fn new(_opt: ArgOptions) -> Self { Self {} } }
84/// // in awesome_cli_lib
85/// #[non_exhaustive]
86/// # #[derive(Default)]
87/// struct ArgOptions {
88///     pub name: String,
89///     pub short: Option<String>,
90///     pub long: Option<String>,
91///     pub help: Option<String>,
92///     pub required: bool,
93///     pub takes_value: bool,
94///     pub multiple: bool,
95///     pub default_value: Option<String>,
96/// }
97///
98/// impl ArgOptions {
99///     pub fn new(name: String) -> Self {
100///         // ...
101/// #       Self { name, ..Default::default() }
102///     }
103/// }
104///
105/// // your crate
106/// use assign::assign;
107///
108/// let arg = Arg::new(assign!(ArgOptions::new("version".into()), {
109///     short: Some("V".into()),
110///     long: Some("version".into()),
111///     help: Some("prints the version and quits.".into()),
112/// }));
113/// ```
114#[macro_export]
115macro_rules! assign {
116    ($initial_value:expr, {
117        $( $field:ident $( : $value:expr )? ),+ $(,)?
118    }) => ({
119        let mut item = $initial_value;
120        $( $crate::assign!(@assign item $field $( : $value )?); )+
121        item
122    });
123    (@assign $item:ident $field:ident : $value:expr) => {
124        $item.$field = $value;
125    };
126    (@assign $item:ident $field:ident) => {
127        $item.$field = $field;
128    };
129}
130
131#[cfg(test)]
132mod tests {
133    #[derive(Debug, Default, PartialEq)]
134    struct SomeStruct {
135        a: u32,
136        b: Option<f32>,
137        c: Option<u64>,
138    }
139
140    #[test]
141    fn basic() {
142        let res = assign!(SomeStruct::default(), {
143            a: 5,
144            b: None,
145        });
146
147        assert_eq!(
148            res,
149            SomeStruct {
150                a: 5,
151                b: None,
152                c: None
153            }
154        );
155    }
156
157    #[test]
158    fn shorthand() {
159        let def = SomeStruct::default();
160        let a = 5;
161        let res = assign!(def, { a });
162
163        assert_eq!(
164            res,
165            SomeStruct {
166                a: 5,
167                b: None,
168                c: None
169            }
170        );
171    }
172
173    #[test]
174    fn field_expr_inference() {
175        let b = 0.0.into();
176        let res = assign!(SomeStruct::default(), {
177            b,
178            c: 1.into(),
179        });
180
181        assert_eq!(
182            res,
183            SomeStruct {
184                a: 0,
185                b: Some(0.0),
186                c: Some(1)
187            }
188        );
189    }
190
191    #[test]
192    fn all_fields() {
193        let a = 1;
194        let b = Some(1.0);
195
196        let res = assign!(SomeStruct::default(), {
197            a,
198            b,
199            c: 1.into(),
200        });
201
202        assert_eq!(
203            res,
204            SomeStruct {
205                a: 1,
206                b: Some(1.0),
207                c: Some(1),
208            }
209        );
210    }
211}