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}