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