condtype/
lib.rs

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
13pub 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/// ```
61pub 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]
213macro_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]
247macro_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]
259macro_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)]
270pub 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.
295mod 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}