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}
350