powerbool/
lib.rs

1//! Semantically enriched value mutation.
2
3/// This trait adds ergonomic convenience functions to `bool`.
4pub trait PowerBool {
5    /// Set the value to `true`, and say if it changed.
6    #[inline(always)]
7    fn raise(&mut self) -> bool {
8        self.set(true)
9    }
10
11    /// Set the value to `false`, and say if it changed.
12    #[inline(always)]
13    fn lower(&mut self) -> bool {
14        self.set(false)
15    }
16
17    /// Set the value, and say if it changed.
18    ///
19    /// Don't use a boolean literal with this function, `val` should be an expression:
20    ///
21    /// * Instead of `set(true)`, use `raise()`.
22    /// * Instead of `set(false)`, use `lower()`.
23    #[inline(always)]
24    fn set(&mut self, val: bool) -> bool;
25
26    /// Returns `true` if the value was `false`. This is not the same as `set()`.
27    ///
28    /// ```
29    /// use powerbool::PowerBool;
30    /// let list = vec![0.23, 3.4, 8.0, 9.6];
31    /// let mut found = false;
32    /// for x in list {
33    ///     if found.trip((x as i32 as f32) == x) {
34    ///         println!("Alert! There's an integer hiding in our number!");
35    ///         return;
36    ///     }
37    /// }
38    /// println!("Nobody here but us floats.");
39    /// # panic!();
40    /// ```
41    #[inline(always)]
42    fn trip(&mut self, val: bool) -> bool;
43
44    /// Makes sure the value is `false`, and return `true` if it didn`t change.
45    /// (Am I kicking a dead horse?)
46    #[inline(always)]
47    fn kick(&mut self) -> bool {
48        !self.set(false)
49    }
50
51    /// Makes sure the value is `true`, and return `true` if it didn't change.
52    /// (The opposite of `kick`: am I punching a sleeping horse?)
53    #[inline(always)]
54    fn punch(&mut self) -> bool {
55        !self.set(true)
56    }
57}
58
59impl PowerBool for bool {
60    #[inline(always)]
61    fn set(&mut self, val: bool) -> bool {
62        let ret = *self != val;
63        *self = val;
64        ret
65    }
66    
67    #[inline(always)]
68    fn trip(&mut self, val: bool) -> bool {
69        let ret = val && !*self;
70        if ret {
71            *self = true;
72        }
73        ret
74    }
75}
76
77#[cfg(test)]
78mod test_powerbool {
79    //! It would be silly to have two names for the same truth table.
80    //!
81    //! But some things are acceptable:
82    //!
83    //!  * Eliminating a bool literal
84    //!  * Eliminating a return value negation
85    //!  * Eliminating a following value change
86    //!
87    //! What's not okay?
88    //!
89    //!  * `foo()` being logically identical to `bar()`. (Unless there's some kind of semantic/usage
90    //!  distinction. But maybe that point we'd want actual new types.)
91    use super::PowerBool;
92
93    fn truth_table_row<F: Fn(&mut bool) -> bool>(before: bool, f: F, returns: bool, after: bool) {
94        let mut v = before;
95        assert_eq!(f(&mut v), returns);
96        assert_eq!(v, after);
97    }
98
99    #[test]
100    fn set() {
101        fn set_true(v: &mut bool) -> bool { v.set(true) }
102        fn set_false(v: &mut bool) -> bool { v.set(false) }
103        truth_table_row(true, set_true, false, true);
104        truth_table_row(true, set_false, true, false);
105        truth_table_row(false, set_true, true, true);
106        truth_table_row(false, set_false, false, false);
107    }
108
109    #[test]
110    fn raise() {
111        truth_table_row(true, bool::raise, false, true);
112        truth_table_row(false, bool::raise, true, true);
113    }
114
115    #[test]
116    fn lower() {
117        truth_table_row(true, bool::lower, true, false);
118        truth_table_row(false, bool::lower, false, false);
119    }
120
121    #[test]
122    fn trip() {
123        fn trip_true(v: &mut bool) -> bool { v.trip(true) }
124        fn trip_false(v: &mut bool) -> bool { v.trip(false) }
125        truth_table_row(true, trip_true, false, true);
126        truth_table_row(true, trip_false, false, true);
127        truth_table_row(false, trip_true, true, true);
128        truth_table_row(false, trip_false, false, false);
129    }
130
131    #[test]
132    fn kick() {
133        truth_table_row(false, bool::kick, true, false);
134        truth_table_row(true, bool::kick, false, false);
135    }
136
137    #[test]
138    fn punch() {
139        truth_table_row(false, bool::punch, false, true);
140        truth_table_row(true, bool::punch, true, true);
141    }
142}
143
144pub trait Change {
145    /// If this value does not equal the other value, become that value and return true.
146    #[inline(always)]
147    fn change(&mut self, v: Self) -> bool;
148}
149impl<T: PartialEq> Change for T {
150    #[inline(always)]
151    fn change(&mut self, v: Self) -> bool {
152        if self != &v {
153            *self = v;
154            true
155        } else {
156            false
157        }
158    }
159}
160
161#[cfg(test)]
162mod test_change {
163    use super::Change;
164
165    #[test]
166    fn works() {
167        let mut x = 1;
168        assert!(x.change(2));
169    }
170
171    #[test]
172    fn still_works() {
173        let mut x = 1;
174        assert!(!x.change(1));
175    }
176}