reducer/reducer.rs
1#[cfg(feature = "alloc")]
2mod arc;
3#[cfg(feature = "alloc")]
4mod boxed;
5#[cfg(feature = "alloc")]
6mod rc;
7mod tuple;
8
9/// Trait for types that represent the logical state of an application.
10///
11/// Perhaps a more accurate mental model for types that implement this trait is that of a
12/// _state machine_, where the nodes correspond to the universe of all possible representable
13/// values and the edges correspond to _actions_.
14pub trait Reducer<A> {
15 /// Implements the transition given the current state and an action.
16 ///
17 /// This method is expected to have no side effects and must never fail.
18 /// In many cases, an effective way to handle illegal state transitions is to make
19 /// them idempotent, that is to leave the state unchanged.
20 ///
21 /// # Example
22 ///
23 /// ```rust
24 /// use reducer::Reducer;
25 ///
26 /// #[derive(Debug)]
27 /// struct Todos(Vec<String>);
28 ///
29 /// // Actions
30 /// struct Create(String);
31 /// struct Remove(usize);
32 ///
33 /// impl Reducer<Create> for Todos {
34 /// fn reduce(&mut self, Create(todo): Create) {
35 /// self.0.push(todo);
36 /// }
37 /// }
38 ///
39 /// impl Reducer<Remove> for Todos {
40 /// fn reduce(&mut self, Remove(i): Remove) {
41 /// if i < self.0.len() {
42 /// self.0.remove(i);
43 /// } else {
44 /// // Illegal transition, leave the state unchanged.
45 /// }
46 /// }
47 /// }
48 ///
49 /// let mut todos = Todos(vec![]);
50 ///
51 /// todos.reduce(Create("Buy milk".to_string()));
52 /// println!("{:?}", todos); // ["Buy milk"]
53 ///
54 /// todos.reduce(Create("Learn Reducer".to_string()));
55 /// println!("{:?}", todos); // ["Buy milk", "Learn Reducer"]
56 ///
57 /// todos.reduce(Remove(42)); // out of bounds
58 /// println!("{:?}", todos); // ["Buy milk", "Learn Reducer"]
59 ///
60 /// todos.reduce(Remove(0));
61 /// println!("{:?}", todos); // ["Learn Reducer"]
62 /// ```
63 fn reduce(&mut self, action: A);
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use mockall::{predicate::*, *};
70 use test_strategy::proptest;
71
72 mock! {
73 pub Reducer<A: 'static> {
74 pub fn id(&self) -> usize;
75 }
76
77 impl<A: 'static> Reducer<A> for Reducer<A> {
78 fn reduce(&mut self, action: A);
79 }
80
81 impl<A: 'static> Clone for Reducer<A> {
82 fn clone(&self) -> Self;
83 }
84 }
85
86 #[proptest]
87 fn reduce(action: u8) {
88 let mut mock = MockReducer::new();
89
90 mock.expect_reduce()
91 .with(eq(action))
92 .once()
93 .return_const(());
94
95 let reducer: &mut dyn Reducer<_> = &mut mock;
96 reducer.reduce(action);
97 }
98}
99
100#[cfg(test)]
101pub(crate) use self::tests::MockReducer;