1# Tips for embedded C developers 2 3This chapter collects a variety of tips that might be useful to experienced 4embedded C developers looking to start writing Rust. It will especially 5highlight how things you might already be used to in C are different in Rust. 6 7## Preprocessor 8 9In embedded C it is very common to use the preprocessor for a variety of 10purposes, such as: 11 12* Compile-time selection of code blocks with `#ifdef` 13* Compile-time array sizes and computations 14* Macros to simplify common patterns (to avoid function call overhead) 15 16In Rust there is no preprocessor, and so many of these use cases are addressed 17differently. In the rest of this section we cover various alternatives to 18using the preprocessor. 19 20### Compile-Time Code Selection 21 22The closest match to `#ifdef ... #endif` in Rust are [Cargo features]. These 23are a little more formal than the C preprocessor: all possible features are 24explicitly listed per crate, and can only be either on or off. Features are 25turned on when you list a crate as a dependency, and are additive: if any crate 26in your dependency tree enables a feature for another crate, that feature will 27be enabled for all users of that crate. 28 29[Cargo features]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section 30 31For example, you might have a crate which provides a library of signal 32processing primitives. Each one might take some extra time to compile or 33declare some large table of constants which you'd like to avoid. You could 34declare a Cargo feature for each component in your `Cargo.toml`: 35 36```toml 37[features] 38FIR = [] 39IIR = [] 40``` 41 42Then, in your code, use `#[cfg(feature="FIR")]` to control what is included. 43 44```rust 45/// In your top-level lib.rs 46 47#[cfg(feature="FIR")] 48pub mod fir; 49 50#[cfg(feature="IIR")] 51pub mod iir; 52``` 53 54You can similarly include code blocks only if a feature is _not_ enabled, or if 55any combination of features are or are not enabled. 56 57Additionally, Rust provides a number of automatically-set conditions you can 58use, such as `target_arch` to select different code based on architecture. For 59full details of the conditional compilation support, refer to the 60[conditional compilation] chapter of the Rust reference. 61 62[conditional compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html 63 64The conditional compilation will only apply to the next statement or block. If 65a block can not be used in the current scope then the `cfg` attribute will 66need to be used multiple times. It's worth noting that most of the time it is 67better to simply include all the code and allow the compiler to remove dead 68code when optimising: it's simpler for you and your users, and in general the 69compiler will do a good job of removing unused code. 70 71### Compile-Time Sizes and Computation 72 73Rust supports `const fn`, functions which are guaranteed to be evaluable at 74compile-time and can therefore be used where constants are required, such as 75in the size of arrays. This can be used alongside features mentioned above, 76for example: 77 78```rust 79const fn array_size() -> usize { 80 #[cfg(feature="use_more_ram")] 81 { 1024 } 82 #[cfg(not(feature="use_more_ram"))] 83 { 128 } 84} 85 86static BUF: [u32; array_size()] = [0u32; array_size()]; 87``` 88 89These are new to stable Rust as of 1.31, so documentation is still sparse. The 90functionality available to `const fn` is also very limited at the time of 91writing; in future Rust releases it is expected to expand on what is permitted 92in a `const fn`. 93 94### Macros 95 96Rust provides an extremely powerful [macro system]. While the C preprocessor 97operates almost directly on the text of your source code, the Rust macro system 98operates at a higher level. There are two varieties of Rust macro: _macros by 99example_ and _procedural macros_. The former are simpler and most common; they 100look like function calls and can expand to a complete expression, statement, 101item, or pattern. Procedural macros are more complex but permit extremely 102powerful additions to the Rust language: they can transform arbitrary Rust 103syntax into new Rust syntax. 104 105[macro system]: https://doc.rust-lang.org/book/ch19-06-macros.html 106 107In general, where you might have used a C preprocessor macro, you probably want 108to see if a macro-by-example can do the job instead. They can be defined in 109your crate and easily used by your own crate or exported for other users. Be 110aware that since they must expand to complete expressions, statements, items, 111or patterns, some use cases of C preprocessor macros will not work, for example 112a macro that expands to part of a variable name or an incomplete set of items 113in a list. 114 115As with Cargo features, it is worth considering if you even need the macro. In 116many cases a regular function is easier to understand and will be inlined to 117the same code as a macro. The `#[inline]` and `#[inline(always)]` [attributes] 118give you further control over this process, although care should be taken here 119as well — the compiler will automatically inline functions from the same crate 120where appropriate, so forcing it to do so inappropriately might actually lead 121to decreased performance. 122 123[attributes]: https://doc.rust-lang.org/reference/attributes.html#inline-attribute 124 125Explaining the entire Rust macro system is out of scope for this tips page, so 126you are encouraged to consult the Rust documentation for full details. 127 128## Build System 129 130Most Rust crates are built using Cargo (although it is not required). This 131takes care of many difficult problems with traditional build systems. However, 132you may wish to customise the build process. Cargo provides [`build.rs` 133scripts] for this purpose. They are Rust scripts which can interact with the 134Cargo build system as required. 135 136[`build.rs` scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html 137 138Common use cases for build scripts include: 139 140* provide build-time information, for example statically embedding the build 141 date or Git commit hash into your executable 142* generate linker scripts at build time depending on selected features or other 143 logic 144* change the Cargo build configuration 145* add extra static libraries to link against 146 147At present there is no support for post-build scripts, which you might 148traditionally have used for tasks like automatic generation of binaries from 149the build objects or printing build information. 150 151### Cross-Compiling 152 153Using Cargo for your build system also simplifies cross-compiling. In most 154cases it suffices to tell Cargo `--target thumbv6m-none-eabi` and find a 155suitable executable in `target/thumbv6m-none-eabi/debug/myapp`. 156 157For platforms not natively supported by Rust, you will need to build `libcore` 158for that target yourself. On such platforms, [Xargo] can be used as a stand-in 159for Cargo which automatically builds `libcore` for you. 160 161[Xargo]: https://github.com/japaric/xargo 162 163## Iterators vs Array Access 164 165In C you are probably used to accessing arrays directly by their index: 166 167```c 168int16_t arr[16]; 169int i; 170for(i=0; i<sizeof(arr)/sizeof(arr[0]); i++) { 171 process(arr[i]); 172} 173``` 174 175In Rust this is an anti-pattern: indexed access can be slower (as it needs to 176be bounds checked) and may prevent various compiler optimisations. This is an 177important distinction and worth repeating: Rust will check for out-of-bounds 178access on manual array indexing to guarantee memory safety, while C will 179happily index outside the array. 180 181Instead, use iterators: 182 183```rust,ignore 184let arr = [0u16; 16]; 185for element in arr.iter() { 186 process(*element); 187} 188``` 189 190Iterators provide a powerful array of functionality you would have to implement 191manually in C, such as chaining, zipping, enumerating, finding the min or max, 192summing, and more. Iterator methods can also be chained, giving very readable 193data processing code. 194 195See the [Iterators in the Book] and [Iterator documentation] for more details. 196 197[Iterators in the Book]: https://doc.rust-lang.org/book/ch13-02-iterators.html 198[Iterator documentation]: https://doc.rust-lang.org/core/iter/trait.Iterator.html 199 200## References vs Pointers 201 202In Rust, pointers (called [_raw pointers_]) exist but are only used in specific 203circumstances, as dereferencing them is always considered `unsafe` -- Rust 204cannot provide its usual guarantees about what might be behind the pointer. 205 206[_raw pointers_]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer 207 208In most cases, we instead use _references_, indicated by the `&` symbol, or 209_mutable references_, indicated by `&mut`. References behave similarly to 210pointers, in that they can be dereferenced to access the underlying values, but 211they are a key part of Rust's ownership system: Rust will strictly enforce that 212you may only have one mutable reference _or_ multiple non-mutable references to 213the same value at any given time. 214 215In practice this means you have to be more careful about whether you need 216mutable access to data: where in C the default is mutable and you must be 217explicit about `const`, in Rust the opposite is true. 218 219One situation where you might still use raw pointers is interacting directly 220with hardware (for example, writing a pointer to a buffer into a DMA peripheral 221register), and they are also used under the hood for all peripheral access 222crates to allow you to read and write memory-mapped registers. 223 224## Volatile Access 225 226In C, individual variables may be marked `volatile`, indicating to the compiler 227that the value in the variable may change between accesses. Volatile variables 228are commonly used in an embedded context for memory-mapped registers. 229 230In Rust, instead of marking a variable as `volatile`, we use specific methods 231to perform volatile access: [`core::ptr::read_volatile`] and 232[`core::ptr::write_volatile`]. These methods take a `*const T` or a `*mut T` 233(_raw pointers_, as discussed above) and perform a volatile read or write. 234 235[`core::ptr::read_volatile`]: https://doc.rust-lang.org/core/ptr/fn.read_volatile.html 236[`core::ptr::write_volatile`]: https://doc.rust-lang.org/core/ptr/fn.write_volatile.html 237 238For example, in C you might write: 239 240```c 241volatile bool signalled = false; 242 243void ISR() { 244 // Signal that the interrupt has occurred 245 signalled = true; 246} 247 248void driver() { 249 while(true) { 250 // Sleep until signalled 251 while(!signalled) { WFI(); } 252 // Reset signalled indicator 253 signalled = false; 254 // Perform some task that was waiting for the interrupt 255 run_task(); 256 } 257} 258``` 259 260The equivalent in Rust would use volatile methods on each access: 261 262```rust,ignore 263static mut SIGNALLED: bool = false; 264 265#[interrupt] 266fn ISR() { 267 // Signal that the interrupt has occurred 268 // (In real code, you should consider a higher level primitive, 269 // such as an atomic type). 270 unsafe { core::ptr::write_volatile(&mut SIGNALLED, true) }; 271} 272 273fn driver() { 274 loop { 275 // Sleep until signalled 276 while unsafe { !core::ptr::read_volatile(&SIGNALLED) } {} 277 // Reset signalled indicator 278 unsafe { core::ptr::write_volatile(&mut SIGNALLED, false) }; 279 // Perform some task that was waiting for the interrupt 280 run_task(); 281 } 282} 283``` 284 285A few things are worth noting in the code sample: 286 * We can pass `&mut SIGNALLED` into the function requiring `*mut T`, since 287 `&mut T` automatically converts to a `*mut T` (and the same for `*const T`) 288 * We need `unsafe` blocks for the `read_volatile`/`write_volatile` methods, 289 since they are `unsafe` functions. It is the programmer's responsibility 290 to ensure safe use: see the methods' documentation for further details. 291 292It is rare to require these functions directly in your code, as they will 293usually be taken care of for you by higher-level libraries. For memory mapped 294peripherals, the peripheral access crates will implement volatile access 295automatically, while for concurrency primitives there are better abstractions 296available (see the [Concurrency chapter]). 297 298[Concurrency chapter]: ../concurrency/index.md 299 300## Packed and Aligned Types 301 302In embedded C it is common to tell the compiler a variable must have a certain 303alignment or a struct must be packed rather than aligned, usually to meet 304specific hardware or protocol requirements. 305 306In Rust this is controlled by the `repr` attribute on a struct or union. The 307default representation provides no guarantees of layout, so should not be used 308for code that interoperates with hardware or C. The compiler may re-order 309struct members or insert padding and the behaviour may change with future 310versions of Rust. 311 312```rust 313struct Foo { 314 x: u16, 315 y: u8, 316 z: u16, 317} 318 319fn main() { 320 let v = Foo { x: 0, y: 0, z: 0 }; 321 println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z); 322} 323 324// 0x7ffecb3511d0 0x7ffecb3511d4 0x7ffecb3511d2 325// Note ordering has been changed to x, z, y to improve packing. 326``` 327 328To ensure layouts that are interoperable with C, use `repr(C)`: 329 330```rust 331#[repr(C)] 332struct Foo { 333 x: u16, 334 y: u8, 335 z: u16, 336} 337 338fn main() { 339 let v = Foo { x: 0, y: 0, z: 0 }; 340 println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z); 341} 342 343// 0x7fffd0d84c60 0x7fffd0d84c62 0x7fffd0d84c64 344// Ordering is preserved and the layout will not change over time. 345// `z` is two-byte aligned so a byte of padding exists between `y` and `z`. 346``` 347 348To ensure a packed representation, use `repr(packed)`: 349 350```rust 351#[repr(packed)] 352struct Foo { 353 x: u16, 354 y: u8, 355 z: u16, 356} 357 358fn main() { 359 let v = Foo { x: 0, y: 0, z: 0 }; 360 // Unsafe is required to borrow a field of a packed struct. 361 unsafe { println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z) }; 362} 363 364// 0x7ffd33598490 0x7ffd33598492 0x7ffd33598493 365// No padding has been inserted between `y` and `z`, so now `z` is unaligned. 366``` 367 368Note that using `repr(packed)` also sets the alignment of the type to `1`. 369 370Finally, to specify a specific alignment, use `repr(align(n))`, where `n` is 371the number of bytes to align to (and must be a power of two): 372 373```rust 374#[repr(C)] 375#[repr(align(4096))] 376struct Foo { 377 x: u16, 378 y: u8, 379 z: u16, 380} 381 382fn main() { 383 let v = Foo { x: 0, y: 0, z: 0 }; 384 let u = Foo { x: 0, y: 0, z: 0 }; 385 println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z); 386 println!("{:p} {:p} {:p}", &u.x, &u.y, &u.z); 387} 388 389// 0x7ffec909a000 0x7ffec909a002 0x7ffec909a004 390// 0x7ffec909b000 0x7ffec909b002 0x7ffec909b004 391// The two instances `u` and `v` have been placed on 4096-byte alignments, 392// evidenced by the `000` at the end of their addresses. 393``` 394 395Note we can combine `repr(C)` with `repr(align(n))` to obtain an aligned and 396C-compatible layout. It is not permissible to combine `repr(align(n))` with 397`repr(packed)`, since `repr(packed)` sets the alignment to `1`. It is also not 398permissible for a `repr(packed)` type to contain a `repr(align(n))` type. 399 400For further details on type layouts, refer to the [type layout] chapter of the 401Rust Reference. 402 403[type layout]: https://doc.rust-lang.org/reference/type-layout.html 404 405## Other Resources 406 407* In this book: 408 * [A little C with your Rust](../interoperability/c-with-rust.md) 409 * [A little Rust with your C](../interoperability/rust-with-c.md) 410* [The Rust Embedded FAQs](https://docs.rust-embedded.org/faq.html) 411* [Rust Pointers for C Programmers](http://blahg.josefsipek.net/?p=580) 412* [I used to use pointers - now what?](https://github.com/diwic/reffers-rs/blob/master/docs/Pointers.md) 413