overflow_proof/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::cmp::{Eq, PartialEq};
4use std::marker::PhantomData;
5use std::ops::{Add, Deref, Div, Mul, Sub};
6
7/// Base traits & ops for inner types wrapped by `Checked` and `Unchecked`
8// Why weren't these made into stdlib traits from the ground up?! :(
9mod base_checked_ops;
10pub use base_checked_ops::*;
11
12/// Marker for [`Checked`] that can be converted to the inner type semi-automatically.
13#[derive(Debug)]
14pub struct WithDeref;
15
16/// Marker for [`Checked`] that must be explicitly converted to the inner type.
17#[derive(Debug)]
18pub struct WithoutDeref;
19
20/// A wrapper around a numeric type, containing a valid value,
21/// that will perform overflow checks on arithmetic operations.
22///
23/// Any arithmetic operation (like `Add`, `Sub`...) will be performed using checked
24/// arithmetic and return a [`Unchecked`] type which will track overflow state. Additional
25/// chaining additional arithmetic operations is possible witoutchecking overflow in intermediate steps.
26/// Only when the final result is to be used, it's neccessary to call [`Unchecked::check()`]
27/// to convert back to [`Checked`] value.
28///
29/// `T` is the inner type (`u8`, `i16`, etc.) wrapped by this type.
30///
31/// `D` is a marker controlling automatic conversion to inner type. It defaults to [`WithDeref`]
32/// which results in semi-implicit conversion to `T` available (like `Deref`). For values where handling
33/// overflow is particularily important and opting out of it could have serious consequences,
34/// [`WithoutDeref`] can be used, which will require calling
35/// an explicit conversion function to convert to the inner type.
36#[derive(Debug)]
37pub struct Checked<T, D = WithDeref> {
38    v: T,
39    _deref: PhantomData<D>,
40}
41
42impl<T, D> Clone for Checked<T, D>
43where
44    T: Clone,
45{
46    fn clone(&self) -> Self {
47        Self {
48            v: self.v.clone(),
49            _deref: self._deref,
50        }
51    }
52}
53
54impl<T, D> Copy for Checked<T, D> where T: Copy {}
55
56impl<T, D> From<T> for Checked<T, D> {
57    fn from(v: T) -> Self {
58        Self { v, _deref: PhantomData }
59    }
60}
61
62impl<T, D> Checked<T, D> {
63    pub fn into_inner(self) -> T {
64        self.v
65    }
66}
67
68impl<T, D> Checked<T, D>
69where
70    T: Clone,
71{
72    pub fn to_inner(&self) -> T {
73        self.v.clone()
74    }
75
76}
77
78impl<T> Checked<T, WithDeref> {
79    pub fn new_with_deref(v: T) -> Checked<T, WithDeref> {
80        Self {
81            v,
82            _deref: PhantomData,
83        }
84    }
85
86    pub fn new(v: T) -> Self {
87        Self {
88            v,
89            _deref: PhantomData,
90        }
91    }
92}
93
94impl<T> Checked<T, WithoutDeref> {
95    pub fn new_without_deref(v: T) -> Checked<T, WithoutDeref> {
96        Self {
97            v,
98            _deref: PhantomData,
99        }
100    }
101
102}
103
104impl<T> Deref for Checked<T, WithDeref> {
105    type Target = T;
106
107    fn deref(&self) -> &Self::Target {
108        &self.v
109    }
110}
111
112
113/// Intermediate result of artimetic operations on [`Checked`] value that might contain overflow
114///
115/// At any point contains either a value of the inner type `T`,
116/// or a marker that overflow was detected and any subsequent
117/// artimetic operations will keep resulting
118/// in overflow, similiarly to how NaN behaves.
119#[derive(Debug)]
120pub struct Unchecked<T, D = WithoutDeref> {
121    v: Option<T>,
122    _deref: PhantomData<D>,
123}
124
125impl<T, D> Clone for Unchecked<T, D>
126where
127    T: Clone,
128{
129    fn clone(&self) -> Self {
130        Self {
131            v: self.v.clone(),
132            _deref: self._deref,
133        }
134    }
135}
136
137impl<T, D> Copy for Unchecked<T, D> where T: Copy {}
138
139impl<T, D> Unchecked<T, D> {
140    /// Convert back to [`Checked`].
141    ///
142    /// Returns `None` if inner value denotes overflow.
143    pub fn check(self) -> Option<Checked<T, D>> {
144        self.v.map(|v| Checked {
145            v,
146            _deref: PhantomData,
147        })
148    }
149}
150
151macro_rules! impl_op {
152    ($op:tt,$checked_op:tt,$method:ident,$checked_method:ident) => {
153        impl<T, D, Rhs> $op<Rhs> for Checked<T, D>
154        where
155            T: $checked_op<Rhs>,
156        {
157            type Output = Unchecked<<T as $checked_op<Rhs>>::Output, D>;
158
159            fn $method(self, rhs: Rhs) -> Self::Output {
160                Unchecked {
161                    v: self.v.$checked_method(rhs),
162                    _deref: self._deref,
163                }
164            }
165        }
166
167        impl<T, D, Rhs> $op<Rhs> for Unchecked<T, D>
168        where
169            T: $checked_op<Rhs>,
170        {
171            type Output = Unchecked<<T as $checked_op<Rhs>>::Output, D>;
172
173            fn $method(self, rhs: Rhs) -> Self::Output {
174                Unchecked {
175                    v: self.v.and_then(|v| v.$checked_method(rhs)),
176                    _deref: self._deref,
177                }
178            }
179        }
180    };
181}
182
183impl_op!(Add, CheckedAdd, add, checked_add);
184impl_op!(Sub, CheckedSub, sub, checked_sub);
185impl_op!(Mul, CheckedMul, mul, checked_mul);
186impl_op!(Div, CheckedDiv, div, checked_div);
187
188impl<T, D1, D2> PartialEq<Checked<T, D1>> for Checked<T, D2>
189where
190    T: PartialEq<T>,
191{
192    fn eq(&self, other: &Checked<T, D1>) -> bool {
193        self.v.eq(&other.v)
194    }
195}
196impl<T, D1> Eq for Checked<T, D1> where T: PartialEq<T> {}
197
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn it_works() {
205        assert_eq!(
206            *{ Checked::new_with_deref(1u8) * 12 - 2 }
207                .check()
208                .expect("no oveflow"),
209            10
210        );
211
212        assert_eq!(
213            { Checked::new(1u8) * 12 - 2 }.check().expect("no oveflow"),
214            Checked::new(10)
215        );
216
217        assert!({ Checked::new(1u8) + u8::MAX }.check().is_none());
218        assert!({ Checked::new(255u8) + 5 - 100 }.check().is_none());
219    }
220}