//! Possibly uninitialized values. //! //! This modules defines the `Nillable` datatype which implements //! values of type `T` with an extra element `Nil` that contaminates any //! expression it is a part of. //! Unlike `Option`, `Nillable` implements all the binary operators //! that one can apply to `T`. //! //! E.g. //! - `Defined(5) + Defined(6)` is `Defined(11)` //! - `Defined(5) + Nil` is `Nil` //! - `Nil + Defined(0.1)` is `Nil` use std::fmt; /// Values of `T` or `Nil`. /// /// Nil can implement most operations by mapping, and unlike `Option` it /// has a canonical way to define operators by systematically /// using the `Option` monad. #[derive(Debug, Clone, Copy)] pub enum Nillable { /// Something. Defined(T), /// Nothing. Nil, } pub use Nillable::*; /// The default value of a `Nillable` is always `Nil`. impl Default for Nillable { #[inline] fn default() -> Self { Nil } } impl fmt::Display for Nillable { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Nil => write!(f, "nil"), Defined(t) => write!(f, "{t}"), } } } impl Nillable { /// This function is the identity, but its type constraints /// can help the compiler determine the associated type `T` for `Nil`. #[inline] #[must_use] pub fn with_type_of(self, _other: &Self) -> Self { self } } impl Nillable { /// Extract the inner value. /// /// # Panics /// /// if `self` is `Nil`. #[inline] #[track_caller] #[expect(clippy::panic, reason = "Panic at the semantic level")] pub fn unwrap(self) -> T { match self { Defined(t) => t, Nil => panic!("Tried to unwrap a nil value"), } } } impl Nillable { /// Apply the given function to the inner value. #[inline] pub fn map(self, f: F) -> Nillable where F: FnOnce(T) -> U, { match self { Defined(t) => Defined(f(t)), Nil => Nil, } } } /// `Nillable` implements all standard binary operators (not comparison /// operators though) by mapping them to the inner value if it exists. macro_rules! nillable_impl_ops_binary { ($trait:ident, $func:ident) => { impl std::ops::$trait for Nillable where T: std::ops::$trait, { type Output = Self; #[inline(always)] fn $func(self, other: Self) -> Self { match (self, other) { (Defined(lhs), Defined(rhs)) => Defined(lhs.$func(rhs)), _ => Nil, } } } }; } nillable_impl_ops_binary!(Add, add); nillable_impl_ops_binary!(Mul, mul); nillable_impl_ops_binary!(Sub, sub); nillable_impl_ops_binary!(Div, div); nillable_impl_ops_binary!(Rem, rem); nillable_impl_ops_binary!(BitOr, bitor); nillable_impl_ops_binary!(BitXor, bitxor); nillable_impl_ops_binary!(BitAnd, bitand); /// `Nillable` implements all standard unary operators by mapping them to the /// inner value if it exists. macro_rules! nillable_impl_ops_unary { ($trait:ident, $func:ident) => { impl std::ops::$trait for Nillable where T: std::ops::$trait, { type Output = Self; #[inline(always)] fn $func(self) -> Self { match self { Defined(lhs) => Defined(lhs.$func()), _ => Nil, } } } }; } nillable_impl_ops_unary!(Not, not); nillable_impl_ops_unary!(Neg, neg); impl Nillable where T: PartialEq, { /// Equality test for `Nillable`. /// /// `Nillable` does not implement equality, because `Nil` contaminates /// all expressions it is a part of. /// The function `eq` implements only a partial equality that /// is not reflexive where `Nil` is not comparable to itself. #[inline] pub fn eq(self, other: Self) -> Option { match (self, other) { (Defined(this), Defined(other)) => Some(this == other), _ => None, } } /// Identity test for `Nillable`. /// /// Determines whether `self` and `other` are identical. /// `Nil` is identical to itself, and two `Defined(_)` are /// identical if their inner values are `PartialEq`. #[inline] pub fn is(&self, other: Self) -> bool { match (self, other) { (Defined(this), Defined(other)) => this == &other, (Nil, Nil) => true, _ => false, } } } /// Identity assertion. /// /// Since `Nillable` does not implement `PartialEq`, /// the canonical way to perform equality assertions is /// `assert_is!(a, b)` that panics if `a` and `b` are not identical. #[macro_export] macro_rules! assert_is { ($lhs:expr, $rhs:expr) => { if !$lhs.is($rhs) { panic!("{} is not identical to {}", $lhs, $rhs.with_type_of(&$lhs)); } }; } impl Nillable where T: PartialOrd, { /// Partial ordering of `Nillable`s. /// /// Similarly to equality, `Nillable` does not implement `PartialOrd` /// because `Nil` is incomparable with every other value. #[inline] pub fn cmp(self, other: Self) -> Option { match (self, other) { (Defined(this), Defined(other)) => this.partial_cmp(&other), _ => None, } } } /// A trait to generate a tuple of `Nil` of the right arity. /// /// In any place where a `(Nillable<_>, Nillable<_>, ...)` is expected, /// (including nested components), you can use `AllNil::auto_size()` /// to get a default value. /// /// Implementations are currently provided for tuples of length up to 10. pub trait AllNil { /// Construct a default value. fn auto_size() -> Self; } impl AllNil for Nillable { #[inline] fn auto_size() -> Self { Nil } } /// Default implementation for a tuple: maps to all elements. macro_rules! all_nil_for_tuple { ( ( $( $T:ty ),* ) with $($decl:tt)*) => { impl$($decl)* AllNil for ( $( $T, )* ) where $( $T : AllNil ),* { #[inline] fn auto_size() -> Self { ( $( <$T as AllNil>::auto_size(), )* ) } } } } all_nil_for_tuple!((T0) with ); all_nil_for_tuple!((T0, T1) with ); all_nil_for_tuple!((T0, T1, T2) with ); all_nil_for_tuple!((T0, T1, T2, T3) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4, T5) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7, T8) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) with ); all_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) with ); #[test] fn nil_of_size() { let _: Nillable = AllNil::auto_size(); let _: (Nillable, Nillable) = AllNil::auto_size(); let _: Nillable<()> = AllNil::auto_size(); let _: ( ((Nillable,),), Nillable, Nillable, Nillable, Nillable, ) = AllNil::auto_size(); let _ = if true { Defined(1) } else { AllNil::auto_size() }; } /// Check if the tuple has a non-nil first element. pub trait FirstIsNil { /// (Recursively) query the head of the tuple until a base type. fn first_is_nil(&self) -> bool; } impl FirstIsNil for Nillable { #[inline] fn first_is_nil(&self) -> bool { matches!(self, Nil) } } /// Default implementation for a tuple: projects to the first element. macro_rules! first_is_nil_for_tuple { ( ( $( $T:ty ),* ) with $($decl:tt)*) => { impl$($decl)* FirstIsNil for ( $( $T, )* ) where $( $T : FirstIsNil ),* { #[inline] fn first_is_nil(&self) -> bool { self.0.first_is_nil() } } } } first_is_nil_for_tuple!((T0) with ); first_is_nil_for_tuple!((T0, T1) with ); first_is_nil_for_tuple!((T0, T1, T2) with ); first_is_nil_for_tuple!((T0, T1, T2, T3) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4, T5) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7, T8) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) with ); first_is_nil_for_tuple!((T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) with );