The type state pattern

  1. #[entry]
  2. fn main() -> ! {
  3.     let p = Peripherals::take().unwrap();
  4.     let gpio0 = p0::Parts::new(p.P0);
  5.     let pin: P0_01<Disconnected> = gpio0.p0_01;
  6.     // let gpio0_01_again = gpio0.p0_01; // Error, moved.
  7.     let mut pin_input: P0_01<Input<Floating>> = pin.into_floating_input();
  8.     if pin_input.is_high().unwrap() {
  9.         // ...
  10.     }
  11.     let mut pin_output: P0_01<Output<OpenDrain>> = pin_input
  12.         .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low);
  13.     pin_output.set_high().unwrap();
  14.     // pin_input.is_high(); // Error, moved.
  15.     let _pin2: P0_02<Output<OpenDrain>> = gpio0
  16.         .p0_02
  17.         .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low);
  18.     let _pin3: P0_03<Output<PushPull>> =
  19.         gpio0.p0_03.into_push_pull_output(Level::Low);
  20.     loop {}
  21. }
  • Pins don’t implement Copy or Clone, so only one instance of each can exist. Once a pin is moved out of the port struct nobody else can take it.
  • Changing the configuration of a pin consumes the old pin instance, so you can’t keep use the old instance afterwards.
  • The type of a value indicates the state that it is in: e.g. in this case, the configuration state of a GPIO pin. This encodes the state machine into the type system, and ensures that you don’t try to use a pin in a certain way without properly configuring it first. Illegal state transitions are caught at compile time.
  • You can call is_high on an input pin and set_high on an output pin, but not vice-versa.
  • Many HAL crates follow this pattern.