The Detroit Post
Monday, 25 October, 2021

Rust For Embedded

Earl Hamilton
• Sunday, 25 October, 2020
• 27 min read

Due to the easy cross-compilation and the availability of many high quality crates on crates.Io we quickly ended up with a fast and robust demonstrator. Thanks to Rust, we can take memory safety for granted, while other benefits of a zero-overhead language with a sophisticated type system help us develop maintainable software.

jasper types conglomerate matrix quartz yellow embedded specimen consider within would


Share current best practices about using Rust for embedded development. i.e. How to best use Rust language features to write more correct embedded software.

This book tries to be as general as possible but to make things easier for both the readers and the writers it uses the ARM Cortex-M architecture in all its examples. You should also be familiar with the idioms of the 2018 edition as this book targets Rust 2018.

You are comfortable developing and debugging embedded systems in another language such as C, C++, or Ada, and are familiar with concepts such as: Cross Compilation Memory Mapped Peripherals Interrupts Common interfaces such as I2C, SPI, Serial, etc. This book will be using the STM32F3DISCOVERY development board from Microelectronics for the majority of the examples contained within.

This board is based on the ARM Cortex-M architecture, and while basic functionality is the same across most CPUs based on this architecture, peripherals and other implementation details of Microcontrollers are different between different vendors, and often even different between Microcontroller families from the same vendor. For this reason, we suggest purchasing the STM32F3DISCOVERY development board for the purpose of following the examples in this book.

While this document takes a casual approach to Rust, language-lawyer-ey notes are included in footnotes. Rustc, the standard Rust compiler, has excellent error messages.

rust warning then 8th

Matt God bolt’s Compiler Explorer is useful for getting a feel for the assembly that Rust produces. C++ users are cautioned: Rust shares a lot of terminology and concepts (ownership, lifetimes, instructors, polymorphism) with it, but Rust ’s realization of them tends to have significantly different semantics.

Rust is a general-purpose programming language with a focus on maximum programmer control and zero runtime overhead, while eliminating the sharp edges traditionally associated with such languages. Syntactically and philosophically, Rust most resembles C++ and ML (a family of functional programming languages), though semantically it is very different from both.

Rust is the first popular, well-supported language that provides absolute memory safety without the use of automatic reference counting or a garbage collector. This is primarily achieved through a technology called the borrow checker, which uses source code annotations to statically prove that memory is not accessed after it is no longer valid, with no runtime checking.

Rust compiles to native code and rivals C and C++ for memory and compute performance, and can seamlessly integrate with anything using a C calling convention. It also statically eliminates a large class of memory bugs associated with security vulnerabilities.

A complete Rust tool chain consists of a few major parts: Cargo, a build system for Rust (though rust can be invoked directly or by other tools).

rust serious removal

Some unstable features are very useful for embedded, so it is not uncommon for embeddedRust projects to use a nightly compiler. A file named rust -toolchain in a project directory can be used to the same effect.

It can build projects (that is, directories with a Cargo.Tom file in them) and their dependencies automatically. Individual units of compilation in Rust are called “crates”, which are either static libraries (i.e., comparable to a.a file) or fully linked native binaries.

Also, the contents of the Rustlers environment variable are passed to rust, as a mechanism for injecting flags. The Rust standard library, like LBC, is smaller in embedded environments.

The standard library consists of three crates: core, allow, and std. Core, sometimes called lib core, is all the fundamental definitions, which don’t rely on operating system support.

Std is core + allow, as well as OS APIs like file and thread support. The # drama disables std and allow, leaving behind core.


Throughout this document, we will only use core types, though we may refer to them through the std namespace (they’re aliases). --target is also accepted directly by Cargo, unlike most rust flags.

Integers Rust lacks C’s int, long, unsigned, and other types with an implementation-defined size. Rust supports all the usual binary operations on all of its integer types, though you can’t mix different types when doing arithmetic, and, unlike in C, there is no integer promotion.

Overflow in Rust is different from C: it is implementation-defined, and must either crash the program or wrap around. Casting is done with the as keyword, and behaves exactly the same way as in C: (uint8_t) x is written x as u8.

Integer types never implicitly convert between each other, even between signed and unsigned variants. Rust has the usual integer literals: 123 for decimal, 0xdead for hex, 0b1010 for binary, and 0o777 for octal.

Underscores may be arbitrarily interspersed throughout an integer literal to separate groups of digits: 0xdead_beef, 1_000_000. It is not implicitly convertible with integers, and is otherwise an u8 that is guaranteed to have either the value 0×00 or 0×01, with respective literals false and true.

septoria wheat leaf disease blotch pepper update mississippi lesion grains note inside

This is guaranteed to lay out fields in declaration order, adding padding for alignment. The empty tuple type, (), is called “unit”, and serves as Rust ’s void type (unlike void, () has exactly one value, also named (), which is zero-sized).

This makes it possible to use == on Instruct values, which will just perform field-wise equality. The same can be done for ordering operations like < and >= : # will define comparison functions that compare structs lexicographically.

Ends and Unions Much like C, Rust has enumerations for describing a type with a fixed set of values: Note that, unlike structs, the variants of an ENIM are automatically public.

To get an ENIM whose discriminant are allocated like in C, we can employ a rear attribute: Unlike C, though, Rust will only guarantee discriminant values that are explicitly written down.

Such ends may be safely cast into integer types (like Lyceum::Apple as u32), but not back: the compiler always assumes that the underlying value of a Lyceum is 0, 5, or 7, and violating this constraint is Undefined Behavior (Up). If we want to require that an ENIM is an exact integer width, we can use #, where T is an integral type like u16 or i8.


Unions also have restrictions on what types can be used as variants, due to concerns about instructors. Array elements can be accessed with x, exactly like in C. Note, however, that Rust automatically inserts bounds checks around every array access; failing the bounds check will trigger a panic in the program.

Unsafe Rust can be used to cheat the bounds check when it is known (to the programmer, not rust!) As we’ll learn later, Rust has different aliasing rules for references that are both more powerful and which the compiler can check automatically.

Usize, size, and all pointer types may be freely cast back and forth. Rust pointers do not support arithmetic operators; instead, a method fills this role: instead of PTR + 4, write PTR.offset(4).

When pointers are referenced, they must be well-aligned and point to valid memory, like in C; failure to do so is Up. Pointer reference is still subject to move semantics, like in normal Rust.

The read() and write() methods on pointers can be used to ignore these rules. Read_unaligned() and write_unaligned() can be used to perform safe unaligned access, and copy_to() and copy_nonoverlapping_to() are analogous to remove() and memory(), respectively.

nodorum blotch disease guide wheat identification crops diseases field purple brown leaves affecting

Volatile operations are also performed using pointer methods, which are discussed separately later on. Since all of these operations reference a pointer, they naturally are restricted to Unsafe Rust.

In general, raw pointers are only used in Rust to point to potentially uninitialized memory and generally denote addresses, rather than references to actual memory. These, along with the type definitions above, are called items in the grammar, to avoid confusion with C’s declaration/definition distinction.

Unlike C, Rust does not have forward declaration or declaration-order semantics; everything is visible to the entire file. The type is required here, and the right-hand side must be a constant expression, which is roughly any combination of literals, numeric operators, and coast FN s (more on those in a moment).

They can be thought of best as fixed expressions that get copy+pasted into wherever they get used, similar to #define s and ENIM declaratory in C. It is possible to take the address of a constant, but it is not guaranteed to be consistent. Global are guaranteed to live, .data, or .BSS, depending on their mutability and initialization.

Mutable global are particularly dangerous, since they can be a source of data races in multicore systems. Mutable global scan also be a source other racy behavior due to IRQ control flow.


As such, reading or writing to a mutable global, or creating a reference to one, requires Unsafe Rust. The body of a function consists of a list of statements, potentially ending in an expression; that expression is the function’s return value (no return keyword needed).

Items can be mixed in with the statements, which are local to their current scope but visible in all of it. “C” is the only one we really care about, which switches the calling convention to the system’s C ABI.

The default, implicit calling convention is ex tern Rust ". Untangled functions can then be called by C, allowing a Rust library to have a C interface.

Will implicitly coerce to all types (this simplifies type-checking, and is perfectly fine since this all occurs in provably-dead code). This makes the function available for constant evaluation, but greatly restricts the operations available.

The type after the : is optional, and if missing, the compiler will use all information in the current scope, both before and after the let, to infer one. Like in C, reassignment is an expression, but unlike in C, it evaluates to () rather than to the assigned value.

danielle colby cushman diesel dannie taringa imagenes

Like in almost all other languages, literals, operators, function calls, variable references, and so on are all standard expressions, which we’ve already seen how Rust spells already. A block is delimited by {}, consists of a set of a list of statements and items, and potentially an ending expression, much like a function.

Blocks are like local functions that execute immediately, and can be useful for constraining the scope of variables. This automatic () is important when dealing with constructs like if and match expressions that need to unify the types of multiple branches of execution into one.

Conditionals: if and match Rust ’s if expression is, syntactically, similar to C’s. If expressions evaluate to the value of the block that winds up getting executed.

For example, the following won’t compile, because an errant semicolon causes type checking to fail: In general, it is a good idea to end all final expressions in if clauses with a semicolon, unless its value is needed.

Rust calls these case clauses “match arms”. Match statements do not have fall through, unlike C. In particular, only one arm is ever executed.

gormley anthony sculptures antony museum blockworks philadelphia sculpture whyy emma lee walks terrace among upper loom rocky steps iron

Rust will statically check that every possible case is covered. Under the hood, all of Rust ’s control flow is implemented in terms of loop, match, and break.

Control Flow Rust has return, break, and continue, which have their usual meanings from C. They are also expressions, and, much like loop {}, have type ! One of Rust ’s great benefits is mostly-seamless interop with existing C libraries.

(), which will eventually be replaced with a Rust -specific syntax that better integrates with the language. Many of these subtly affect linker/optimizer behavior, and are very much in the “you probably don’t need to worry about it” category.

# can also be used to pessimize inclining for functions that are unlikely to ever be called. The previous part established enough vocabulary to take roughly any embedded C program and manually translate it into Rust.

Double-free and use-after-free are a common source of crashes and security vulnerabilities in C code. This particular class of errors (which don’t directly involve pointers) are prevented by move semantics.

centaur fence horse

This is why moves are not relevant when handling integers and raw pointers: they’re all Copy types. Note that any structs and ends you define won’t be Copy by default, even if all of their fields are.

This function is obviously wrong, but such bugs, where a pointer outlives the data it points to, are as insidious as they are common in C. Rust ’s primary pointer type, references, make this impossible.

This program will fail to compile with a cryptic error: missing lifetime specifier. Clearly, we’re missing something, but at least the compiler didn’t let this obviously wrong program through.

This <'a> syntax can also be applied to items like structs: If you’re creating a type containing a reference, the <'a> is required: A shared reference, at, provides immutable access to a value of type T, and can be freely duplicated: at is Copy.

A unique reference, smut T, provides mutable access to a value of type T, but is subject to Rust ’s aliasing rules, which are far more restrictive than C’s strict aliasing, and can’t be turned off. This means more optimization opportunities, without safe code needing to do anything.

casting sand pouring metal foundry mold

Finally, it should go without saying that references are only useful for main memory; Rust is entitled to generate spurious loads and stores for (possibly unused!) References, so MIMO should be performed exclusively through raw pointers.

Because it is statically known that every reference, at all times, points to a valid, initialized value of type T, explicit referencing is elided most of the time (though, when necessary, they can be referenced: *x is a value that can be assigned to). This applies even for heavily nested references: the dot operator on AT&T will trigger three memory lookups.

References can be coerced into raw pointers: x as *coast T, and compared directly. While Rust is not an object-oriented language, it does provide a mechanism for name spacing functions under types: imply (for implementation) blocks.

Outside modules cannot access anything not marked as pub, allowing us to enforce an invariant on Counter : it is monotonic. Inherent functions don’t take a self, and are called like Counter::new().

Methods are really just normal functions: you could also have called this like Counter::inc(smut my_counter). Note that calling a function that takes self or smut self triggers a borrow of the receiving type; if a self function is called on a non-reference value, the value will have its address taken, which gets past into the method.

fallout 76 knob spruce mountain locations south survey cheats markers overlooking bog lie segmentnext

As we’ve already seen, many primitive types have methods, too; these are defined in special imply blocks in the standard library. References also do not allow for pointer arithmetic, so an &u32 cannot be used to point to a buffer of words.

Then, a slice* would point to a length followed by that many T s; it can’t reasonably exist except behind a pointer. Similarly, is what Rust calls a dynamically sized type, which needs to exist behind a reference: it is much more common to see & and smut .

This can be combined with the _ pattern to simply repeat an operation n times: One important note with slices, as pertains to borrowing, is unique references.

It is usually possible to structure code in such a way that avoids it, but this escape hatch exists for when necessary. Slices can also be decomposed into their pointer and length parts, using the as_ptr() and Len() functions, and reassembled with std::slice::from_raw_parts().

Raw strings disable escape sequences, and are delimited by an arbitrary, matching number of pound signs: Rust also has character literals in the form of 'z', though their type is char, a 32-bit Unicode code-point.

metal paint texture grey painted dirty textures gray scratches background 8bit

Instructors are special functions that perform cleanup logic when a value has become unreachable (i.e., both the let that originally declared it can no longer be named, and the last reference to it expired). After the destruct or is run, each of the value’s fields, if it’s a struct or ENIM, are also destroyed (or “dropped”).

Instructors are declared with a special kind of imply block (we’ll see more like this, later): If several values go out of scope simultaneously, they are dropped in reverse order of declaration.

The drop method can’t be called manually; however, the standard library function std::me::drop() can be used to give up ownership of a value and immediately destroy it. Instructors enable the resource acquisition is initialization (Rail) idiom.

The classic example of Rail is dynamic memory management: you allocate memory with mallow, stash the returned pointer in a struct, and then that struct’s destruct or calls free on that pointer. Since the struct has gone out of scope when free is called, UHF is impossible.

Thanks to Rust ’s move semantics, this struct can’t be duplicated, so the destruct or can’t be called twice. In some situations, calling a destruct or might be undesirable (for example, during certain Unsafe Rust operations).

The standard library provides the special function std::me::forget(), which consumes a value without calling its destruct or. The std::me::ManuallyDrop type is a smart pointer that holds a T, while inhibiting its destruct or.

Std::PTR::drop_in_place() can be used to run the destruct or in the value behind a raw pointer, without technically giving up access to it. Option is a standard library type representing a “possibly absent T ”.

The is similar to the lifetime syntax we saw before; it means that Option is a generic type ; we’ll dig into those soon. The key thing pattern matching gives us is the ability to inspect the union within an ENIM safely: the tag check is enforced by the compiler.

This pattern is exactly like _, but it binds the matched value to its name. The binding can be made mutable by writing Some(but val) instead.

This gives us a consistent way to spell “I want a duplicate of this value”. The standard library provides traits for a number of similar operations, such as Default, for providing a default value, Partial and EQ, for equality, Partial and ORD, for ordering, and Hash, for non-cryptographic hashing.

The # syntax described in the “Ownership” section can be used with any of these traits to automatically implement them for a type. This last syntax is also useful in generic contexts, or for being precise about the exact function being referred to.

Dyn Trait is a dynamically-sized type, much like slices, and can only exist behind a pointer. In other words, all the functions must treat Self as if it were not sized and only accessible through a pointer.

Unsafe traits typically enforce some kind of additional constraint in addition to their methods; in fact, unsafe traits frequently don’t have methods at all. For example, the standard library trait Sync is implemented by all types which synchronize access.

They’re like the opposite of derive() traits, which you need to opt into, since they meaningfully affect the API of your type in a way that it is important to be able to control. Generic programming is writing source code that can be compiled for many types.

This function accepts a value of any type and immediately returns it. Using a generic function with all of its type parameters filled in causes it to be instantiated (or metamorphized), resulting in code being generated for it.

This process essentially consists of replacing each occurrence of T with its concrete value. Overzealous use of generic code can lead to binary bloat.

Note that this bound is included in a where clause, after the return type. This is identical to placing it in the angle brackets, but is recommended for complicated bounds to keep them out of the way.

Phantom Data The following struct definition is an error in Rust : This is not always ideal, since it’s sometimes useful to expose a T in your type even though you don’t own one; we can work around this using the compiler’s suggestion: Phantasmata.

For more information on how to use it, refer to the type documentation or the relevant Rustonomicon entry. In Rust, a “smart pointer” is any type that implements std::ops::Dark, the reference operator.

Implementing the Dark trait gives a type T two features: It can be referenced: *x becomes syntax sugar for *(x.dark()) or *(x.dark_but()), depending on whether the resulting value is assigned to.

It is not uncommon for generic wrapper types, which restrict access to a value, to be smart pointers. While not quite as relevant to smart pointers, the Index and Index traits are analogous to the Dark and Darfur traits, which enable the x subscript syntax.

Closures are not mere function pointers, because of this captured state. Similarly, Rust closures need extra state to execute, except are becomes part of the start_routine value.

Not only that, Rust will synthesize a bespoke context struct for are, where normally the programmer would need to do this manually. As we’ll see, Rust has a number of different ABIS for closures, some of which closely resemble what pthread_create does; in some cases, the function pointer and its context can even be inclined.

To be polymorphic over different closure types, we use the special FN, Input, and Nonce traits. These traits use special syntax similar to function pointers.

For example, FN(i32) i32 represents taking an i32 argument and returning another i32. Closures as Function Arguments There are roughly two ways of writing a function that accepts a closure argument: through dynamic dispatch, or through static dispatch, which have a performance and a size penalty, respectively.

FN and Input closures can be accepted using trait objects: This is completely identical to the C approach: the synthetic function lives in the trait object table, while the captures are behind the actual trait object pointer itself.

Of course, the table call has a performance penalty, but avoids the code size overhead of generic instantiation. The pseudo type imply Trait can be used in function argument position to say “this parameter can be of any type that implements Trait ”, which effectively declares an anonymous generic parameter.

In a non- embedded context, the solution (as suggested by the compiler) would be to allocate the closures on the heap, and use trait objects. However, allocation is limited in embedded contexts, so this solution is unavailable.

If none of the closures capture, returning a function pointer may be an acceptable solution: In general, function pointers are easiest, and the requirement of no captures is not particularly onerous.

Short-lived structs can also try to use trait objects, but the lifetime requirement can be fairly limiting: As we saw above, Option is a type that lets us specify a “potentially uninitialized” value.

The Rust language guarantees that Option is identical to a nullable pointer at the ABI layer, so it can be safely passed that way into C code. A Result represents a completed computation of a value of type T that might have gone wrong.

In a way, Option is just a Result, where the error type is just the trivial unit tuple. Computations that are executed for their side effects which can fail, such as a write, tend to return Result<(), E>.

An iterator that eventually produces None, and then forever more returns None, is called “fused”. Changes the Item type from T into (size, T), tracking the current index in the sequence along with the value.

A number of other traits can enhance the properties of an iterator, enabling further methods: ExactSizeIterator iterators produce a known, fixed number of values; DoubleEndedIterators can have elements pulled from both the front, and the back of the sequence. While many of the operations above have naive implementations in terms of next, standard library iterators will override them when a more efficient algorithm is available.

In general, iterators can produce very efficient code similar to that emitted by while loops, but care should be taken when using especially complex chains of combinations. Each Rust crate is (from the compiler’s perspective) given a unique, single-identifier name.

Use statements can also be marked with visibility: this will cause the imported symbols to become part of the module. This is how many fundamental core types are accessible through the std crate as well.

Rust does not have headers, or declaration order constraints; modules within a crate can freely form cyclic dependencies, since they are not units of compilation, merely namespaces. Interior mutability is a borrow check escape hatch for working around Rust ’s aliasing rules.

Normally, Rust requires that you prove statically that you have unique access to a value before you mutate it. Unsafely is a special, compiler-blessed type which contains a single T, and has a method FN get(self) *but T.

This makes it possible to safely mutate code that is known, at runtime, to be uniquely owned. Of course, it is very unsafe to use Unsafely directly, and exists to form the basis of other abstractions.

The Cell approach simply never creates a unique reference at all: instead, it holds a valid T at all times, and provides a swap primitive for taking out the T and leaving behind another one. This way, no aliasing rules need to be enforced, since no reference actually points to that T.

The Recall approach instead does basic borrow checking at runtime. In addition to holding a T, a Recall holds a counter of the number of outstanding shared references (or a sentinel value for an outstanding unique reference).

The try_borrow() and try_borrow_but() methods dynamically check if such a borrow is valid (no outstanding unique references, or no outstanding references at all, respectively), and return a Result to indicate success or failure. Other abstractions can be built on top of Unsafely that maintain the aliasing invariants with other strategies, but they will ultimately be analogous to one of Cell or Recall.

Interior mutability is also one of the main differences between a constant and a static: This illustrates another property of Unsafely : it causes data that is otherwise declared as immutable to be allocated as mutable.

Importantly, all of these actions require uttering the keyword unsafe, so that they can be easily detected in code review. Code inside unsafe blocks says to the reader that the programmer has checked subtle safety guarantees that the compiler cannot on its own.

The canonical reference is the Rustonomicon, a non-normative document describing common uses for Unsafe Rust. With these powers comes responsibility: Unsafe Rust is not safe from Undefined Behavior, and cannot leave the machine in a state where actions allowed in normal, safe Rust would trigger Undefined Behavior.

Every unsafe FN should declare, in documentation, what invariants it assumes that the caller will uphold, and what state it will leave the machine in. For example, <>::get_unchecked(n) elides the bounds check for the indexing operation, and it is up to the caller to uphold it instead.

Every time unsafe code calls into a non- unsafe function, it must ensure that no violated invariants, which could trigger Undefined Behavior, are observable in that safe code. Unsafe code should be kept to the absolute minimum, and wrapped in safe interfaces that assert invariants, either through static type-system guarantees or through runtime checks.

Every line of unsafe code is a place where the engineering cost of Rust ’s guarantees are wasted. Unsafe Rust is responsible for upholding this core guarantee.

Other Articles You Might Be Interested In

01: Jab Real Estate Stockton Ca
02: Jacana Real Estate St Petersburg Fl
03: Jackie Thomas Real Estate Virginia Beach
04: Jacksonville Amazon Carrier Facility
05: Jacksonville Beach Real Estate Zillow
06: Jacksonville Nc Real Estate Zillow
07: Jacksonville Oregon Real Estate For Rent
08: Jacksonville Real Estate Developers
09: Jacksonville Real Estate Market 2020
10: Jacksonville Real Estate Photographer
1 -
2 -
3 -
4 -