sigma_types/sigma.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
//! Type that maintains a given invariant.
use core::{fmt, ops};
#[cfg(not(debug_assertions))]
use core::marker::PhantomData;
/// Type that maintains a given invariant.
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Sigma<Raw: fmt::Debug, Invariant: crate::Test<Raw>> {
/// Internal type (to which this type will reduce in release builds).
raw: Raw,
/// Function-like type that checks the raw type for a specified invariant.
#[cfg(debug_assertions)]
test: Invariant,
#[cfg(not(debug_assertions))]
phantom: PhantomData<Invariant>,
}
impl<Raw: fmt::Debug, Invariant: crate::Test<Raw>> Sigma<Raw, Invariant> {
/// Create a new sigma type instance by checking an invariant
/// if and only if debug assertions are enabled.
/// # Panics
/// If the invariant does not hold ***and*** debug assertions are enabled.
#[inline]
pub fn check(&self) {
#[expect(
clippy::panic,
reason = "Returning a result would break API in release builds"
)]
match Invariant::test(&self.raw) {
Ok(()) => {}
Err(None) => panic!("{:#?} is not {}", self.raw, Invariant::ADJECTIVE),
Err(Some(message)) => {
panic!("{:#?} is not {}: {message}", self.raw, Invariant::ADJECTIVE);
}
}
}
/// Unwrap the internal value that satisfies the invariant.
/// If you're using this to create another value that should
/// also maintain an invariant, use `map` instead.
#[inline(always)]
pub fn get(self) -> Raw {
self.check();
self.raw
}
/// Unwrap the internal value that satisfies the invariant.
/// If you're using this to create another value that should
/// also maintain an invariant, use `map` instead.
#[inline(always)]
pub fn get_by<Y, F: FnOnce(Raw) -> Y>(self, f: F) -> Y {
f(self.get())
}
/// Unwrap the internal value that satisfies the invariant.
/// If you're using this to create another value that should
/// also maintain an invariant, use `map` instead.
#[inline(always)]
pub fn get_by_mut<Y, F: FnOnce(&mut Raw) -> Y>(&mut self, f: F) -> Y {
f(self.get_mut())
}
/// Unwrap the internal value that satisfies the invariant.
/// If you're using this to create another value that should
/// also maintain an invariant, use `map` instead.
#[inline(always)]
pub fn get_by_ref<Y, F: FnOnce(&Raw) -> Y>(&self, f: F) -> Y {
f(self.get_ref())
}
/// Unwrap the internal value that satisfies the invariant.
/// If you're using this to create another value that should
/// also maintain an invariant, use `map` instead.
#[inline(always)]
pub fn get_mut(&mut self) -> &mut Raw {
self.check();
&mut self.raw
}
/// Unwrap the internal value that satisfies the invariant.
/// If you're using this to create another value that should
/// also maintain an invariant, use `map` instead.
#[inline(always)]
pub fn get_ref(&self) -> &Raw {
self.check();
&self.raw
}
/// Apply a function to a term that implements a given invariant (say, A),
/// then check the output for a (possibly different) invariant (say, B).
#[inline]
pub fn map<
OtherRaw: fmt::Debug,
OtherInvariant: crate::Test<OtherRaw>,
F: FnOnce(Raw) -> OtherRaw,
>(
self,
f: F,
) -> Sigma<OtherRaw, OtherInvariant> {
let raw = self.get();
let other_raw = f(raw);
Sigma::new(other_raw)
}
/// Apply a function to a term that implements a given invariant (say, A),
/// then check the output for a (possibly different) invariant (say, B).
#[inline]
pub fn map_ref<
OtherRaw: fmt::Debug,
OtherInvariant: crate::Test<OtherRaw>,
F: FnOnce(&Raw) -> OtherRaw,
>(
&self,
f: F,
) -> Sigma<OtherRaw, OtherInvariant> {
let raw = self.get_ref();
let other_raw = f(raw);
Sigma::new(other_raw)
}
/// Apply a function that mutates this value,
/// then check that the operation maintained this invariant.
#[inline]
pub fn modify<Y, F: FnOnce(&mut Raw) -> Y>(&mut self, f: F) -> Y {
let raw = self.get_mut();
let y = f(raw);
self.check();
y
}
/// Create a new sigma type instance by checking an invariant.
/// # Panics
/// If the invariant does not hold ***and*** debug assertions are enabled.
#[inline]
pub fn new(raw: Raw) -> Self {
let provisional = Self {
raw,
#[cfg(debug_assertions)]
test: Default::default(),
#[cfg(not(debug_assertions))]
phantom: PhantomData,
};
provisional.check();
provisional
}
}
impl<Raw: fmt::Debug, Invariant: crate::Test<Raw>> ops::Deref for Sigma<Raw, Invariant> {
type Target = Raw;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.get_ref()
}
}