1 | //! [`CondType`]: CondType |
2 | //! [`condval!`]: condval |
3 | //! [`bool`]: bool |
4 | //! [`i32`]: i32 |
5 | //! [`&str`]: str |
6 | //! [`rlim_t::MAX`]: u64::MAX |
7 | //! [Option]: Option |
8 | //! [None]: None |
9 | #![doc = include_str!("../README.md" )] |
10 | #![cfg_attr (not(doc), no_std)] |
11 | #![warn (missing_docs)] |
12 | |
13 | pub mod num; |
14 | |
15 | /// [Conditionally aliases a type](crate#conditional-typing) using a [`bool`] |
16 | /// constant. |
17 | /// |
18 | /// This is the Rust equivalent of [`std::conditional_t` in C++](https://en.cppreference.com/w/cpp/types/conditional). |
19 | /// Unlike the [`Either`] type, the type chosen by `CondType` is aliased, rather |
20 | /// than wrapped with an [`enum`] type. This may be considered a form of [dependent typing](https://en.wikipedia.org/wiki/Dependent_type), |
21 | /// but it is limited in ability and is restricted to compile-time constants |
22 | /// rather than runtime values. |
23 | /// |
24 | /// [`enum`]: https://doc.rust-lang.org/std/keyword.enum.html |
25 | /// [`Either`]: https://docs.rs/either/latest/either/enum.Either.html |
26 | /// |
27 | /// # Examples |
28 | /// |
29 | /// In the following example, `CondType` aliases [`&str`](str) or [`i32`]: |
30 | /// |
31 | /// ``` |
32 | /// use condtype::CondType; |
33 | /// |
34 | /// let str: CondType<true, &str, i32> = "hello" ; |
35 | /// let int: CondType<false, &str, i32> = 42; |
36 | /// ``` |
37 | /// |
38 | /// This can also alias <code>\![Sized]</code> types: |
39 | /// |
40 | /// ``` |
41 | /// # use condtype::CondType; |
42 | /// type T = CondType<true, str, [u8]>; |
43 | /// |
44 | /// let val: &T = "world" ; |
45 | /// ``` |
46 | /// |
47 | /// Rather than assign a value by knowing the condition, this can be used with |
48 | /// [`condval!`] to choose a value based on the same condition: |
49 | /// |
50 | /// ``` |
51 | /// # use condtype::*; |
52 | /// const COND: bool = // ... |
53 | /// # true; |
54 | /// |
55 | /// let val: CondType<COND, &str, i32> = condval!(if COND { |
56 | /// "hello" |
57 | /// } else { |
58 | /// 42 |
59 | /// }); |
60 | /// ``` |
61 | pub type CondType<const B: bool, T, F> = <imp::CondType<B, T, F> as imp::AssocType>::Type; |
62 | |
63 | /// Instantiates a [conditionally-typed](crate#conditional-typing) value. |
64 | /// |
65 | /// Attempting to return different types from [`if`]/[`else`] is not normally |
66 | /// possible since both branches must produce the same type: |
67 | /// |
68 | /// ```compile_fail |
69 | /// let val = if true { "hello" } else { 42 }; |
70 | /// ``` |
71 | /// |
72 | /// This macro enables returning different types by making the type be |
73 | /// conditional on a [`const`] [`bool`]: |
74 | /// |
75 | /// ``` |
76 | /// use condtype::condval; |
77 | /// |
78 | /// let val: &str = condval!(if true { "hello" } else { 42 }); |
79 | /// let val: i32 = condval!(if false { "hello" } else { 42 }); |
80 | /// ``` |
81 | /// |
82 | /// # Performance |
83 | /// |
84 | /// This macro uses a pattern called ["TT munching"](https://veykril.github.io/tlborm/decl-macros/patterns/tt-muncher.html) |
85 | /// to parse the [`if`] condition expression. Compile times for TT munchers are |
86 | /// quadratic relative to the input length, so an expression like `!!!!!!!COND` |
87 | /// will compile slightly slower than `!COND` because it recurses 6 more times. |
88 | /// |
89 | /// This can be mitigated by: |
90 | /// - Moving all logic in the [`if`] condition to a separate [`const`]. |
91 | /// - Wrapping the logic in a block, e.g. `{ !true && !false }`. |
92 | /// |
93 | /// # Examples |
94 | /// |
95 | /// Given two conditions, the following code will construct either a |
96 | /// [`&str`](str), [`i32`], or [`Vec`]: |
97 | /// |
98 | /// ``` |
99 | /// # use condtype::condval; |
100 | /// const COND1: bool = // ... |
101 | /// # true; |
102 | /// const COND2: bool = // ... |
103 | /// # true; |
104 | /// |
105 | /// let str = "hello" ; |
106 | /// let int = 42; |
107 | /// let vec = vec![1, 2, 3]; |
108 | /// |
109 | /// let val = condval!(if COND1 { |
110 | /// str |
111 | /// } else if COND2 { |
112 | /// int |
113 | /// } else { |
114 | /// vec |
115 | /// }); |
116 | /// ``` |
117 | /// |
118 | /// `if let` pattern matching is also supported: |
119 | /// |
120 | /// ``` |
121 | /// # use condtype::*; |
122 | /// const STR: Option<&str> = // ... |
123 | /// # None; |
124 | /// |
125 | /// let val = condval!(if let Some(str) = STR { |
126 | /// str.to_uppercase() |
127 | /// } else { |
128 | /// 42 |
129 | /// }); |
130 | /// ``` |
131 | /// |
132 | /// This macro can be used with [`CondType`] to construct [`const`] values: |
133 | /// |
134 | /// ``` |
135 | /// use condtype::{condval, CondType}; |
136 | /// |
137 | /// const COND: bool = // ... |
138 | /// # true; |
139 | /// |
140 | /// const VAL: CondType<COND, &str, i32> = condval!(if COND { |
141 | /// "hello" |
142 | /// } else { |
143 | /// 42 |
144 | /// }); |
145 | /// ``` |
146 | /// |
147 | /// Each branch is lazily evaluated, so there are no effects from unvisited |
148 | /// branches: |
149 | /// |
150 | /// ``` |
151 | /// # use condtype::*; |
152 | /// let x; |
153 | /// |
154 | /// let val = condval!(if true { |
155 | /// x = 10; |
156 | /// "hello" |
157 | /// } else { |
158 | /// x = 50; |
159 | /// 42 |
160 | /// }); |
161 | /// |
162 | /// assert_eq!(x, 10); |
163 | /// assert_eq!(val, "hello" ); |
164 | /// ``` |
165 | /// |
166 | /// Branch conditions can be any [`bool`] expression. However, see |
167 | /// [performance advice](#performance). |
168 | /// |
169 | /// ``` |
170 | /// # use condtype::*; |
171 | /// let val = condval!(if !true && !false { |
172 | /// "hello" |
173 | /// } else { |
174 | /// 42 |
175 | /// }); |
176 | /// |
177 | /// assert_eq!(val, 42); |
178 | /// ``` |
179 | /// |
180 | /// Assigning an incorrect type will cause a compile failure: |
181 | /// |
182 | /// ```compile_fail |
183 | /// # use condtype::*; |
184 | /// let val: bool = condval!(if true { |
185 | /// "hello" |
186 | /// } else { |
187 | /// 42 |
188 | /// }); |
189 | /// ``` |
190 | /// |
191 | /// Attempting to reuse a non-[`Copy`] value from either branch will cause a |
192 | /// compile failure, because it has been moved into that branch and can thus no |
193 | /// longer be used in the outer context: |
194 | /// |
195 | /// ```compile_fail |
196 | /// # use condtype::*; |
197 | /// let int = 42; |
198 | /// let vec = vec![1, 2, 3]; |
199 | /// |
200 | /// let val = condval!(if true { |
201 | /// int |
202 | /// } else { |
203 | /// vec |
204 | /// }); |
205 | /// |
206 | /// println!("{:?}" , vec); |
207 | /// ``` |
208 | /// |
209 | /// [`const`]: https://doc.rust-lang.org/std/keyword.const.html |
210 | /// [`else`]: https://doc.rust-lang.org/std/keyword.else.html |
211 | /// [`if`]: https://doc.rust-lang.org/std/keyword.if.html |
212 | #[macro_export ] |
213 | macro_rules! condval { |
214 | (if let $pat:pat = $input:block $then:block else $else:block) => { |
215 | $crate::condval!(if { |
216 | #[allow(unused_variables)] |
217 | { ::core::matches!($input, $pat) } |
218 | } { |
219 | if let $pat = $input $then else { |
220 | ::core::unreachable!() |
221 | } |
222 | } else $else) |
223 | }; |
224 | (if let $pat:pat = $input:block $then:block else $($else:tt)+) => { |
225 | $crate::condval!(if let $pat = $input $then else { $crate::condval!($($else)+) }) |
226 | }; |
227 | (if let $pat:pat = $($rest:tt)*) => { |
228 | $crate::__condval_let_parser!($pat, [] $($rest)*) |
229 | }; |
230 | (if $cond:block $then:block else $else:block) => { |
231 | match <() as $crate::__private::If<$cond, _, _>>::PROOF { |
232 | $crate::__private::EitherTypeEq::Left(te) => te.coerce($then), |
233 | $crate::__private::EitherTypeEq::Right(te) => te.coerce($else), |
234 | } |
235 | }; |
236 | (if $cond:block $then:block else $($else:tt)+) => { |
237 | $crate::condval!(if $cond $then else { $crate::condval!($($else)+) }) |
238 | }; |
239 | (if $($rest:tt)*) => { |
240 | $crate::__condval_parser!([] $($rest)*) |
241 | }; |
242 | } |
243 | |
244 | /// Helps `condval!` parse any `if` condition expression by accumulating tokens. |
245 | #[doc (hidden)] |
246 | #[macro_export ] |
247 | macro_rules! __condval_parser { |
248 | ([$($cond:tt)+] $then:block else $($else:tt)+) => { |
249 | $crate::condval!(if { $($cond)+ } $then else $($else)+) |
250 | }; |
251 | ([$($cond:tt)*] $next:tt $($rest:tt)*) => { |
252 | $crate::__condval_parser!([$($cond)* $next] $($rest)*) |
253 | }; |
254 | } |
255 | |
256 | /// Helps `condval!` parse any `if let` input expression by accumulating tokens. |
257 | #[doc (hidden)] |
258 | #[macro_export ] |
259 | macro_rules! __condval_let_parser { |
260 | ($pat:pat, [$($input:tt)+] $then:block else $($else:tt)+) => { |
261 | $crate::condval!(if let $pat = { $($input)+ } $then else $($else)+) |
262 | }; |
263 | ($pat:pat, [$($input:tt)*] $next:tt $($rest:tt)*) => { |
264 | $crate::__condval_let_parser!($pat, [$($input)* $next] $($rest)*) |
265 | }; |
266 | } |
267 | |
268 | /// Pseudo-public implementation details for `condval!`. |
269 | #[doc (hidden)] |
270 | pub mod __private { |
271 | use crate::imp::TypeEq; |
272 | |
273 | pub enum EitherTypeEq<L, R, C> { |
274 | Left(TypeEq<L, C>), |
275 | Right(TypeEq<R, C>), |
276 | } |
277 | |
278 | pub trait If<const B: bool, T, F> { |
279 | type Chosen; |
280 | const PROOF: EitherTypeEq<T, F, Self::Chosen>; |
281 | } |
282 | |
283 | impl<T, F> If<true, T, F> for () { |
284 | type Chosen = T; |
285 | const PROOF: EitherTypeEq<T, F, Self::Chosen> = EitherTypeEq::Left(TypeEq::NEW); |
286 | } |
287 | |
288 | impl<T, F> If<false, T, F> for () { |
289 | type Chosen = F; |
290 | const PROOF: EitherTypeEq<T, F, Self::Chosen> = EitherTypeEq::Right(TypeEq::NEW); |
291 | } |
292 | } |
293 | |
294 | /// Public-in-private implementation details. |
295 | mod imp { |
296 | use core::{marker::PhantomData, mem::ManuallyDrop}; |
297 | |
298 | pub struct CondType<const B: bool, T: ?Sized, F: ?Sized>( |
299 | // `CondType` is covariant over `T` and `F`. |
300 | PhantomData<F>, |
301 | PhantomData<T>, |
302 | ); |
303 | |
304 | pub trait AssocType { |
305 | type Type: ?Sized; |
306 | } |
307 | |
308 | impl<T: ?Sized, F: ?Sized> AssocType for CondType<false, T, F> { |
309 | type Type = F; |
310 | } |
311 | |
312 | impl<T: ?Sized, F: ?Sized> AssocType for CondType<true, T, F> { |
313 | type Type = T; |
314 | } |
315 | |
316 | #[allow (clippy::type_complexity)] |
317 | pub struct TypeEq<T, U>( |
318 | PhantomData<( |
319 | // `TypeEq` is invariant over `T` and `U`. |
320 | fn(T) -> T, |
321 | fn(U) -> U, |
322 | )>, |
323 | ); |
324 | |
325 | impl<T> TypeEq<T, T> { |
326 | pub const NEW: Self = TypeEq(PhantomData); |
327 | } |
328 | |
329 | impl<T, U> TypeEq<T, U> { |
330 | pub const fn coerce(self, from: T) -> U { |
331 | #[repr (C)] |
332 | union Transmuter<From, Into> { |
333 | from: ManuallyDrop<From>, |
334 | into: ManuallyDrop<Into>, |
335 | } |
336 | |
337 | // SAFETY: `TypeEq` instances can only be constructed if `T` and `U` |
338 | // are the same type. |
339 | unsafe { |
340 | ManuallyDrop::into_inner( |
341 | Transmuter { |
342 | from: ManuallyDrop::new(from), |
343 | } |
344 | .into, |
345 | ) |
346 | } |
347 | } |
348 | } |
349 | } |
350 | |