]> git.proxmox.com Git - rustc.git/blame - src/doc/embedded-book/src/peripherals/singletons.md
bump version to 1.79.0+dfsg1-1~bpo12+pve2
[rustc.git] / src / doc / embedded-book / src / peripherals / singletons.md
CommitLineData
9fa01778
XL
1# Singletons
2
3> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object.
4>
5> *Wikipedia: [Singleton Pattern]*
6
7[Singleton Pattern]: https://en.wikipedia.org/wiki/Singleton_pattern
8
9
10## But why can't we just use global variable(s)?
11
12We could make everything a public static, like this
13
532ac7d7 14```rust,ignore
9fa01778
XL
15static mut THE_SERIAL_PORT: SerialPort = SerialPort;
16
17fn main() {
18 let _ = unsafe {
19 THE_SERIAL_PORT.read_speed();
532ac7d7 20 };
9fa01778
XL
21}
22```
23
24But this has a few problems. It is a mutable global variable, and in Rust, these are always unsafe to interact with. These variables are also visible across your whole program, which means the borrow checker is unable to help you track references and ownership of these variables.
25
26## How do we do this in Rust?
27
487cf647 28Instead of just making our peripheral a global variable, we might instead decide to make a structure, in this case called `PERIPHERALS`, which contains an `Option<T>` for each of our peripherals.
9fa01778
XL
29
30```rust,ignore
31struct Peripherals {
32 serial: Option<SerialPort>,
33}
34impl Peripherals {
35 fn take_serial(&mut self) -> SerialPort {
36 let p = replace(&mut self.serial, None);
37 p.unwrap()
38 }
39}
40static mut PERIPHERALS: Peripherals = Peripherals {
41 serial: Some(SerialPort),
42};
43```
44
45This structure allows us to obtain a single instance of our peripheral. If we try to call `take_serial()` more than once, our code will panic!
46
532ac7d7 47```rust,ignore
9fa01778
XL
48fn main() {
49 let serial_1 = unsafe { PERIPHERALS.take_serial() };
50 // This panics!
51 // let serial_2 = unsafe { PERIPHERALS.take_serial() };
52}
53```
54
55Although interacting with this structure is `unsafe`, once we have the `SerialPort` it contained, we no longer need to use `unsafe`, or the `PERIPHERALS` structure at all.
56
57This has a small runtime overhead because we must wrap the `SerialPort` structure in an option, and we'll need to call `take_serial()` once, however this small up-front cost allows us to leverage the borrow checker throughout the rest of our program.
58
59## Existing library support
60
61Although we created our own `Peripherals` structure above, it is not necessary to do this for your code. the `cortex_m` crate contains a macro called `singleton!()` that will perform this action for you.
62
532ac7d7 63```rust,ignore
9ffffee4 64use cortex_m::singleton;
9fa01778
XL
65
66fn main() {
67 // OK if `main` is executed only once
68 let x: &'static mut bool =
69 singleton!(: bool = false).unwrap();
70}
71```
72
73[cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html)
74
f035d41b 75Additionally, if you use [`cortex-m-rtic`](https://github.com/rtic-rs/cortex-m-rtic), the entire process of defining and obtaining these peripherals are abstracted for you, and you are instead handed a `Peripherals` structure that contains a non-`Option<T>` version of all of the items you define.
9fa01778
XL
76
77```rust,ignore
f035d41b
XL
78// cortex-m-rtic v0.5.x
79#[rtic::app(device = lm3s6965, peripherals = true)]
80const APP: () = {
81 #[init]
82 fn init(cx: init::Context) {
83 static mut X: u32 = 0;
84
85 // Cortex-M peripherals
86 let core: cortex_m::Peripherals = cx.core;
87
88 // Device specific peripherals
89 let device: lm3s6965::Peripherals = cx.device;
9fa01778
XL
90 }
91}
9fa01778
XL
92```
93
9fa01778
XL
94## But why?
95
96But how do these Singletons make a noticeable difference in how our Rust code works?
97
98```rust,ignore
99impl SerialPort {
100 const SER_PORT_SPEED_REG: *mut u32 = 0x4000_1000 as _;
101
102 fn read_speed(
103 &self // <------ This is really, really important
104 ) -> u32 {
105 unsafe {
106 ptr::read_volatile(Self::SER_PORT_SPEED_REG)
107 }
108 }
109}
110```
111
112There are two important factors in play here:
113
114* Because we are using a singleton, there is only one way or place to obtain a `SerialPort` structure
115* To call the `read_speed()` method, we must have ownership or a reference to a `SerialPort` structure
116
117These two factors put together means that it is only possible to access the hardware if we have appropriately satisfied the borrow checker, meaning that at no point do we have multiple mutable references to the same hardware!
118
532ac7d7 119```rust,ignore
9fa01778
XL
120fn main() {
121 // missing reference to `self`! Won't work.
122 // SerialPort::read_speed();
123
124 let serial_1 = unsafe { PERIPHERALS.take_serial() };
125
126 // you can only read what you have access to
127 let _ = serial_1.read_speed();
128}
129```
130
131## Treat your hardware like data
132
133Additionally, because some references are mutable, and some are immutable, it becomes possible to see whether a function or method could potentially modify the state of the hardware. For example,
134
135This is allowed to change hardware settings:
136
137```rust,ignore
138fn setup_spi_port(
139 spi: &mut SpiPort,
140 cs_pin: &mut GpioPin
141) -> Result<()> {
142 // ...
143}
144```
145
146This isn't:
147
148```rust,ignore
149fn read_button(gpio: &GpioPin) -> bool {
150 // ...
151}
152```
153
154This allows us to enforce whether code should or should not make changes to hardware at **compile time**, rather than at runtime. As a note, this generally only works across one application, but for bare metal systems, our software will be compiled into a single application, so this is not usually a restriction.