muxado/
constrained.rs

1/// This module contains types and a macro for defining ergonomic newtype
2/// wrappers that ensure that the numeric type they wrap falls within specific
3/// bounds.
4use std::{
5    fmt,
6    ops::RangeInclusive,
7};
8
9#[derive(Debug, Clone)]
10pub struct OutOfRange<T>(pub T, pub RangeInclusive<T>);
11
12impl<T> fmt::Display for OutOfRange<T>
13where
14    T: fmt::Display,
15{
16    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17        write!(
18            f,
19            "value out of range. got {}, expected ({}..={})",
20            self.0,
21            self.1.start(),
22            self.1.end()
23        )
24    }
25}
26
27macro_rules! constrained_num {
28    (clamp; $name:ty, $base:ty, $range:expr) => {
29        impl $name {
30            /// Clamp the provided value to the valid range for this number
31            /// type.
32            pub const fn clamp(value: $base) -> Self {
33                const RANGE: RangeInclusive<$base> = $range;
34                if value > *RANGE.end() {
35                    Self(*RANGE.end())
36                } else if value < *RANGE.start() {
37                    Self(*RANGE.start())
38                } else {
39                    Self(value)
40                }
41            }
42        }
43    };
44    (mask; $name:ty, $base:ty, $range:expr) => {
45        impl $name {
46            /// Mask the provided value using the maximum for this number type.
47            pub const fn mask(value: $base) -> Self {
48                const RANGE: RangeInclusive<$base> = $range;
49                if value < *RANGE.start() {
50                    Self(*RANGE.start())
51                } else if value > *RANGE.end() {
52                    Self(value & *RANGE.end())
53                } else {
54                    Self(value)
55                }
56            }
57        }
58    };
59    ($(#[$outer:meta])* $name:ident, $base:ty, $range:expr, $($t:tt),*) => {
60        $(#[$outer])*
61        ///
62        /// This is a type-safe wrapper for a primitive number type that
63        /// enforces range or bitmask constraints.
64        #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
65        pub struct $name($base);
66
67        #[allow(dead_code, missing_docs)]
68        impl $name {
69            pub const MIN: $base = *$range.start();
70            pub const MAX: $base = *$range.end();
71            pub const BITS: $base = <$base>::BITS - $range.end().leading_zeros();
72        }
73
74        impl Default for $name {
75            fn default() -> Self {
76                Self(*$range.start())
77            }
78        }
79
80        impl fmt::Display for $name {
81            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82                self.0.fmt(f)
83            }
84        }
85
86        impl Deref for $name {
87            type Target = $base;
88            fn deref(&self) -> &$base {
89                &self.0
90            }
91        }
92
93        impl TryFrom<$base> for $name {
94            type Error = OutOfRange<$base>;
95            fn try_from(other: $base) -> Result<Self, Self::Error> {
96                const RANGE: RangeInclusive<$base> = $range;
97                if RANGE.contains(&other) {
98                    Ok(Self(other))
99                } else {
100                    Err(OutOfRange(other, RANGE))
101                }
102            }
103        }
104
105        $(constrained_num!($t; $name, $base, $range);)*
106    };
107    ($(#[$outer:meta])* $name:ident, $base:ty, $range:expr) => {
108        constrained_num!($(#[$outer])* $name, $base, $range, clamp, mask);
109    };
110}