typestates_comparison/
real_types.rs

1//! Every state is implemented as a type parameter of a generic type.
2
3/// An entity in a specific state.
4pub struct Entity<State> {
5    id: u32,
6    state: State,
7}
8
9/// The initial state of a new entity.
10pub struct Initial;
11
12/// The state of an entity after the addition of `a`.
13pub struct WithA {
14    a: f32,
15}
16
17/// The state of an entity after the addition of `b`.
18pub struct WithB {
19    b: bool,
20}
21
22/// The state of an entity after the addition of `c`.
23pub struct WithC {
24    a_or_b: AorB,
25    c: char,
26}
27
28enum AorB {
29    A(f32),
30    B(bool),
31}
32
33impl Entity<Initial> {
34    pub fn new(id: u32) -> Entity<Initial> {
35        Entity { id, state: Initial }
36    }
37
38    pub fn add_a(self, a: f32) -> Entity<WithA> {
39        Entity {
40            id: self.id,
41            state: WithA { a },
42        }
43    }
44
45    pub fn add_b(self, b: bool) -> Entity<WithB> {
46        Entity {
47            id: self.id,
48            state: WithB { b },
49        }
50    }
51}
52
53/// Implemented by entities in states [`WithA`] or [`WithB`].
54pub trait EntityWithAorB {
55    fn add_c(self, c: char) -> Entity<WithC>;
56}
57
58impl EntityWithAorB for Entity<WithA> {
59    fn add_c(self, c: char) -> Entity<WithC> {
60        self.add_c(c)
61    }
62}
63
64impl EntityWithAorB for Entity<WithB> {
65    fn add_c(self, c: char) -> Entity<WithC> {
66        self.add_c(c)
67    }
68}
69
70impl Entity<WithA> {
71    pub fn add_c(self, c: char) -> Entity<WithC> {
72        Entity {
73            id: self.id,
74            state: WithC {
75                a_or_b: AorB::A(self.state.a),
76                c,
77            },
78        }
79    }
80}
81
82impl Entity<WithB> {
83    pub fn add_c(self, c: char) -> Entity<WithC> {
84        Entity {
85            id: self.id,
86            state: WithC {
87                a_or_b: AorB::B(self.state.b),
88                c,
89            },
90        }
91    }
92}
93
94impl Entity<WithC> {
95    pub fn format(&self) -> String {
96        match self.state.a_or_b {
97            AorB::A(a) => format!("id:{id} a:{a} c:{c}", id = self.id, c = self.state.c),
98            AorB::B(b) => format!("id:{id} b:{b} c:{c}", id = self.id, c = self.state.c),
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    #![allow(clippy::approx_constant)]
106
107    use super::*;
108
109    #[test]
110    fn it_works_with_a() {
111        let e = Entity::new(42);
112        let e = e.add_a(3.14);
113        let e = e.add_c('a');
114        assert_eq!(e.format(), format!("id:42 a:3.14 c:a"));
115    }
116
117    #[test]
118    fn it_works_with_b() {
119        let e = Entity::new(42);
120        let e = e.add_b(true);
121        let e = e.add_c('b');
122        assert_eq!(e.format(), format!("id:42 b:true c:b"));
123    }
124}