1# Design Contracts
2
3In our last chapter, we wrote an interface that *didn't* enforce design contracts. Let's take another look at our imaginary GPIO configuration register:
4
5| Name         | Bit Number(s) | Value | Meaning   | Notes |
6| ---:         | ------------: | ----: | ------:   | ----: |
7| enable       | 0             | 0     | disabled  | Disables the GPIO |
8|              |               | 1     | enabled   | Enables the GPIO |
9| direction    | 1             | 0     | input     | Sets the direction to Input |
10|              |               | 1     | output    | Sets the direction to Output |
11| input_mode   | 2..3          | 00    | hi-z      | Sets the input as high resistance |
12|              |               | 01    | pull-low  | Input pin is pulled low |
13|              |               | 10    | pull-high | Input pin is pulled high |
14|              |               | 11    | n/a       | Invalid state. Do not set |
15| output_mode  | 4             | 0     | set-low   | Output pin is driven low |
16|              |               | 1     | set-high  | Output pin is driven high |
17| input_status | 5             | x     | in-val    | 0 if input is < 1.5v, 1 if input >= 1.5v |
18
19If we instead checked the state before making use of the underlying hardware, enforcing our design contracts at runtime, we might write code that looks like this instead:
20
21```rust,ignore
22/// GPIO interface
23struct GpioConfig {
24    /// GPIO Configuration structure generated by svd2rust
25    periph: GPIO_CONFIG,
26}
27
28impl GpioConfig {
29    pub fn set_enable(&mut self, is_enabled: bool) {
30        self.periph.modify(|_r, w| {
31            w.enable().set_bit(is_enabled)
32        });
33    }
34
35    pub fn set_direction(&mut self, is_output: bool) -> Result<(), ()> {
36        if self.periph.read().enable().bit_is_clear() {
37            // Must be enabled to set direction
38            return Err(());
39        }
40
41        self.periph.modify(|r, w| {
42            w.direction().set_bit(is_output)
43        });
44
45        Ok(())
46    }
47
48    pub fn set_input_mode(&mut self, variant: InputMode) -> Result<(), ()> {
49        if self.periph.read().enable().bit_is_clear() {
50            // Must be enabled to set input mode
51            return Err(());
52        }
53
54        if self.periph.read().direction().bit_is_set() {
55            // Direction must be input
56            return Err(());
57        }
58
59        self.periph.modify(|_r, w| {
60            w.input_mode().variant(variant)
61        });
62
63        Ok(())
64    }
65
66    pub fn set_output_status(&mut self, is_high: bool) -> Result<(), ()> {
67        if self.periph.read().enable().bit_is_clear() {
68            // Must be enabled to set output status
69            return Err(());
70        }
71
72        if self.periph.read().direction().bit_is_clear() {
73            // Direction must be output
74            return Err(());
75        }
76
77        self.periph.modify(|_r, w| {
78            w.output_mode.set_bit(is_high)
79        });
80
81        Ok(())
82    }
83
84    pub fn get_input_status(&self) -> Result<bool, ()> {
85        if self.periph.read().enable().bit_is_clear() {
86            // Must be enabled to get status
87            return Err(());
88        }
89
90        if self.periph.read().direction().bit_is_set() {
91            // Direction must be input
92            return Err(());
93        }
94
95        Ok(self.periph.read().input_status().bit_is_set())
96    }
97}
98```
99
100Because we need to enforce the restrictions on the hardware, we end up doing a lot of runtime checking which wastes time and resources, and this code will be much less pleasant for the developer to use.
101
102## Type States
103
104But what if instead, we used Rust's type system to enforce the state transition rules? Take this example:
105
106```rust,ignore
107/// GPIO interface
108struct GpioConfig<ENABLED, DIRECTION, MODE> {
109    /// GPIO Configuration structure generated by svd2rust
110    periph: GPIO_CONFIG,
111    enabled: ENABLED,
112    direction: DIRECTION,
113    mode: MODE,
114}
115
116// Type states for MODE in GpioConfig
117struct Disabled;
118struct Enabled;
119struct Output;
120struct Input;
121struct PulledLow;
122struct PulledHigh;
123struct HighZ;
124struct DontCare;
125
126/// These functions may be used on any GPIO Pin
127impl<EN, DIR, IN_MODE> GpioConfig<EN, DIR, IN_MODE> {
128    pub fn into_disabled(self) -> GpioConfig<Disabled, DontCare, DontCare> {
129        self.periph.modify(|_r, w| w.enable.disabled());
130        GpioConfig {
131            periph: self.periph,
132            enabled: Disabled,
133            direction: DontCare,
134            mode: DontCare,
135        }
136    }
137
138    pub fn into_enabled_input(self) -> GpioConfig<Enabled, Input, HighZ> {
139        self.periph.modify(|_r, w| {
140            w.enable.enabled()
141             .direction.input()
142             .input_mode.high_z()
143        });
144        GpioConfig {
145            periph: self.periph,
146            enabled: Enabled,
147            direction: Input,
148            mode: HighZ,
149        }
150    }
151
152    pub fn into_enabled_output(self) -> GpioConfig<Enabled, Output, DontCare> {
153        self.periph.modify(|_r, w| {
154            w.enable.enabled()
155             .direction.output()
156             .input_mode.set_high()
157        });
158        GpioConfig {
159            periph: self.periph,
160            enabled: Enabled,
161            direction: Output,
162            mode: DontCare,
163        }
164    }
165}
166
167/// This function may be used on an Output Pin
168impl GpioConfig<Enabled, Output, DontCare> {
169    pub fn set_bit(&mut self, set_high: bool) {
170        self.periph.modify(|_r, w| w.output_mode.set_bit(set_high));
171    }
172}
173
174/// These methods may be used on any enabled input GPIO
175impl<IN_MODE> GpioConfig<Enabled, Input, IN_MODE> {
176    pub fn bit_is_set(&self) -> bool {
177        self.periph.read().input_status.bit_is_set()
178    }
179
180    pub fn into_input_high_z(self) -> GpioConfig<Enabled, Input, HighZ> {
181        self.periph.modify(|_r, w| w.input_mode().high_z());
182        GpioConfig {
183            periph: self.periph,
184            enabled: Enabled,
185            direction: Input,
186            mode: HighZ,
187        }
188    }
189
190    pub fn into_input_pull_down(self) -> GpioConfig<Enabled, Input, PulledLow> {
191        self.periph.modify(|_r, w| w.input_mode().pull_low());
192        GpioConfig {
193            periph: self.periph,
194            enabled: Enabled,
195            direction: Input,
196            mode: PulledLow,
197        }
198    }
199
200    pub fn into_input_pull_up(self) -> GpioConfig<Enabled, Input, PulledHigh> {
201        self.periph.modify(|_r, w| w.input_mode().pull_high());
202        GpioConfig {
203            periph: self.periph,
204            enabled: Enabled,
205            direction: Input,
206            mode: PulledHigh,
207        }
208    }
209}
210```
211
212Now let's see what the code using this would look like:
213
214```rust,ignore
215/*
216 * Example 1: Unconfigured to High-Z input
217 */
218let pin: GpioConfig<Disabled, _, _> = get_gpio();
219
220// Can't do this, pin isn't enabled!
221// pin.into_input_pull_down();
222
223// Now turn the pin from unconfigured to a high-z input
224let input_pin = pin.into_enabled_input();
225
226// Read from the pin
227let pin_state = input_pin.bit_is_set();
228
229// Can't do this, input pins don't have this interface!
230// input_pin.set_bit(true);
231
232/*
233 * Example 2: High-Z input to Pulled Low input
234 */
235let pulled_low = input_pin.into_input_pull_down();
236let pin_state = pulled_low.bit_is_set();
237
238/*
239 * Example 3: Pulled Low input to Output, set high
240 */
241let output_pin = pulled_low.into_enabled_output();
242output_pin.set_bit(true);
243
244// Can't do this, output pins don't have this interface!
245// output_pin.into_input_pull_down();
246```
247
248This is definitely a convenient way to store the state of the pin, but why do it this way? Why is this better than storing the state as an `enum` inside of our `GpioConfig` structure?
249
250## Compile Time Functional Safety
251
252Because we are enforcing our design constraints entirely at compile time, this incurs no runtime cost. It is impossible to set an output mode when you have a pin in an input mode. Instead, you must walk through the states by converting it to an output pin, and then setting the output mode. Because of this, there is no runtime penalty due to checking the current state before executing a function.
253
254Also, because these states are enforced by the type system, there is no longer room for errors by consumers of this interface. If they try to perform an illegal state transition, the code will not compile!
255