Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

7.1 Rust on Edge Devices

These next sections provide a hands-on introduction to Rust, focusing on its application in edge programming. We investigate how Rust’s safety, performance, and concurrency features make it ideal for resource-constrained environments such as on edge platforms. Through practical examples, you’ll explore topics such as memory safety, zero-cost abstractions, and building efficient, reliable edge applications, which is important for developers looking to harness Rust’s power for modern IoT and edge embedded systems.

We explore Rust’s distinctive approach to object-oriented programming (OOP). Unlike C++‘s class-based inheritance, Rust achieves similar goals of abstraction and code reuse through a powerful combination of structs and traits. This paradigm shift offers a more explicit and flexible way to define data structures and shared behaviours, crucial for modelling complex systems on resource-constrained edge devices without the overhead of traditional OOP hierarchies.

A significant portion of this section is dedicated to Rust’s foundational memory management model, including memory terminology, variables, memory regions, and a detailed introduction to ownership, borrowing, and lifetimes. These concepts are a little abstract at this point, but they are at the core of Rust’s compile-time memory safety guarantees, eliminating entire classes of bugs prevalent in C/C++ (such as dangling pointers, buffer overflows, and data races) without relying on a garbage collector. For edge programming, where predictable performance and minimal runtime overhead are vital, understanding how Rust statically enforces memory correctness is invaluable.

We then solidify our grasp of the language’s building blocks by examining Rust’s data types (both scalar and compound), functions, and control flow. You’ll see how Rust’s explicit type system, powerful pattern matching, and expression-oriented nature contribute to writing robust and readable code. These features are particularly beneficial in embedded and edge contexts, where clarity and deterministic behaviour are critical for debugging and maintaining systems with limited resources.

Finally, we’ll delve into custom data types, specifically structs (including classic, tuple, and unit structs) and enums. Rust’s enums are far more versatile than their C/C++ counterparts, as they can encapsulate diverse data for each variant, enabling elegant state management and robust error handling. This capability is essential for designing resilient software for edge devices that must respond reliably to various inputs and real-time conditions.

By mastering these fundamental concepts, you’ll gain the tools to leverage Rust’s unique strengths, enabling you to develop highly efficient, secure, and reliable applications for the next generation of edge devices.

Here are some useful references for learning the Rust programming language:

  1. The Rust Programming Language Book (“The Book”) An excellent guide to learning Rust, covering its syntax, ownership model, and key features.
  2. Rust By Example An extensive collection of runnable examples that illustrate various Rust concepts and standard library features.
  3. Rustlings A series of small exercises to get you used to reading and writing Rust code. Ideal for hands-on learning and practice.
  4. The Rust Standard Library Documentation Comprehensive documentation of Rust’s standard library.
  5. The Rustonomicon A deep dive into Rust’s more advanced features, focusing on unsafe code and inner workings.
  6. C++ to Rust Phrasebook a book designed to help C++ programmers learn Rust.

Edge programming traditionally leans heavily on C++ due to its performance and control over hardware. While C++ has long been a strong choice, Rust introduces significant advantages for modern edge development:

  1. Memory Safety Without Garbage Collection: Unlike C++, Rust provides guaranteed memory safety at compile time without relying on garbage collection. This eliminates common issues like null pointer dereferences, buffer overflows, and use-after-free errors, which is vital in resource-constrained edge environments where reliability is critical. In July 2019 Microsoft announced that over 70% of system-level Common Vulnerabilities and Exposures (CVEs) in the previous 12 years related to memory safety bugs1, making the case for the use of modern, safer system programming languages such as Rust .
  2. Concurrency Made Safe: Now that modern edge devices are commonly multi-core devices, they often need to manage multiple tasks concurrently, such as sensor data processing and communication. Rust’s ownership system ensures thread safety without introducing runtime overhead, reducing the risk of race conditions compared to C++‘s manual management of threads.
  3. Zero-Cost Abstractions: Rust offers high-level constructs like iterators and pattern matching, while still compiling down to efficient, low-level machine code. This matches C++‘s performance but with a cleaner, more intuitive syntax that reduces the likelihood of bugs.
  4. Modern Developer Tooling: Rust’s Cargo build system, integrated package management, and built-in testing frameworks streamline development compared to the fragmented tooling ecosystem often associated with C++. These tools enable faster prototyping and deployment on edge devices.
  5. Stronger Guarantees for Cross-Platform Compatibility: Edge applications frequently span diverse architectures and hardware. Rust’s explicit handling of data types and features like no_std (for environments without a standard library) make it easier to write portable, architecture-agnostic code.
  6. Growing Ecosystem for IoT and Embedded Systems: Rust’s growing community and ecosystem offer libraries and frameworks tailored for edge programming, such as tokio for async programming and embedded-hal for hardware abstraction.

By adopting Rust, developers can achieve the performance and control of C++ while significantly reducing development time and the risk of critical bugs, making it a compelling choice for edge programming.

Concept Match

Match Rust Core Advantages

Drag each definition into its matching concept slot, then click Submit. Tap × to return a placed card to the pool.

Memory Safety
drag a definition here…
Safe Concurrency
drag a definition here…
Zero-cost Abstractions
drag a definition here…
Cargo
drag a definition here…
no_std
drag a definition here…

Definition Pool

A feature that enables Rust to run on bare-metal hardware without a standard library.
A unified toolchain for building, testing, and managing dependencies in Rust projects.
High-level constructs that compile to machine code with minimal to no runtime overhead.
Enforced at compile time via ownership, eliminating null pointers and buffer overflows.
Prevents data races without runtime overhead by ensuring exclusive access to data.

Table 1: Here’s a brief comparison table highlighting the key differences between Rust and C++ for edge programming.

FeatureRustC++
Memory SafetyEnforced at compile time with ownership, borrow checking, and no nulls or dangling pointers. Embedded systems do not typically have an MMU making memory safety even more important.Manual memory management; prone to errors like null pointer dereferences and buffer overflows.
ConcurrencyBuilt-in safety guarantees prevent race conditions without runtime overhead.Manual thread management; prone to race conditions and data corruption.
PerformanceComparable to C++ with zero-cost abstractions.High performance but requires manual optimisation.
Error HandlingStrong support with Result and Option types for clear and explicit error handling.Relies on exceptions, which can be harder to trace and manage.
ToolingIntegrated tools like Cargo for building, testing, and dependency management.A fragmented tooling ecosystem often requires multiple external tools.
Learning CurveSteeper due to new concepts like ownership and borrowing.Familiar to developers with a background in traditional OOP languages.
Cross-Platform SupportStrong support with precise control over data types and no_std for embedded systems.Good support but requires careful management of platform-specific dependencies.
Community & EcosystemRapidly growing, with libraries like embedded-hal and async frameworks like tokio.Mature ecosystem with a wide range of established libraries and frameworks.
Code ClarityEncourages cleaner, more maintainable code with strict compiler checks.Allows more flexibility but can lead to harder-to-maintain codebases.
Safety OverheadNo runtime penalty for safety guarantees.

If you are anxious to get started you could try the Rust playground at https://play.rust-lang.org. It is an online platform for experimenting with and sharing Rust code. It provides a browser-based editor with the ability to write, compile, and execute Rust programs without requiring a local installation of the Rust toolchain.

  • Code Editor: Write and modify Rust code directly in the browser.
  • Compiler Options: Choose different Rust versions (e.g., stable, beta, nightly) and enable features like optimisation levels and warnings.
  • Crate Integration: Import and use external crates by specifying dependencies in a virtual Cargo.toml. (See the note below)
  • Execution and Output: Instantly compile and run the code, displaying results in a terminal-like output panel.
  • Sharing and Collaboration: Share code snippets using generated links for easy collaboration.

For example, here is a test application that is linked below:

fn main() {
println!("Derek says, hello!");
}

Link to Playground

Click on RUN button on the top left and you should see the following output:

Standard Error
Compiling playground v0.0.1 (/playground)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
Running `target/debug/playground`
Standard Output
Derek says, hello!

To give you a taste of what Rust looks like in practice, here is a small, elegant program that processes a list of sensor readings. It demonstrates Rust’s clean syntax, its powerful iterator system, and its explicit handling of types, all while maintaining the performance of C++.

fn main() {
// An array of temperature readings from an edge sensor
let readings = [18, 22, 25, 21, 19];
println!("Processing edge sensor data...");
// Calculate the average using a high-level iterator chain
// Zero-cost abstraction as it compiles to efficient machine code
let sum: i32 = readings.iter().sum();
let average = sum as f32 / readings.len() as f32;
println!("The average temperature is {:.1}°C", average);
if average > 20.0 {
println!("Status: Optimal operating conditions.");
} else {
println!("Status: Power-saving mode recommended.");
}
}

This program gives the output (Open in Rust Playground):

Processing edge sensor data...
The average temperature is 21.0°C
Status: Optimal operating conditions.

This example is more than just a “Hello World”; it is a real (though tiny) edge application. It highlights:

  • Type Safety: Notice the explicit conversion using as f32. Rust doesn’t allow silent precision loss, forcing you to be intentional about your data. It is really strict!
  • Expressive Iterators: The readings.iter().sum() chain is highly readable but compiles down to a loop as fast as one you would write by hand in C.
  • Zero-Cost Abstractions: You can use high-level concepts like iterators without worrying about runtime performance penalties as the compiler handles the heavy lifting.

Developing for the edge requires more than just a safe language; it requires an ecosystem that understands the constraints of limited memory, power, and diverse hardware peripherals. Rust’s ecosystem is structured to handle these demands through several key architectural pillars.

In many edge applications, such as those running on a raw microcontroller like the ESP32 that we use in this module, there is no underlying operating system (like Linux or Windows) to provide services like memory allocation or file I/O. Rust handles this through the #![no_std] attribute. By opting out of the standard library (std), your code only uses the core library, which is platform-agnostic and does not require an OS. This results in incredibly small binaries and predictable performance, essential for “bare-metal” programming where you are interacting directly with the processor registers.

One of the greatest challenges in embedded C/C++ is portability; code written for an STM32 I2C peripheral rarely works on an ESP32 without significant rewriting. The Rust community solves this with embedded-hal (Hardware Abstraction Layer). embedded-hal defines a set of traits (which we will see are essentially interfaces) for common peripherals like GPIO, UART, I2C, and SPI.

  • Silicon Providers (like Espressif for the ESP32) provide a crate that implements these traits for their specific hardware.
  • Driver Developers write drivers (e.g., for a temperature sensor) that only depend on the traits, not the specific chip. This means a sensor driver written for Rust can work across any microcontroller that supports embedded-hal, allowing for a massive, reusable driver ecosystem.

Edge devices are often data-gathering nodes. Serialising this data to send over a network (like MQTT or LoRaWAN) must be fast and memory-efficient. serde (Serialisation/Deserialisation) is the gold standard in Rust.

Unlike many C++ libraries that use heavy runtime reflection, serde uses Rust’s powerful macro system to generate serialisation code at compile-time. When combined with compact binary formats like Postcard (designed for no_std), you can serialise complex sensor data structures into a tiny byte array with zero runtime overhead. This is a perfect approach for the energy-conscious AIoT edge.

The ESP32 serves as a perfect example of this ecosystem in action. Whether you are using the dual-core Xtensa variants or the newer RISC-V models (like the ESP32-C3), the community provides:

  • Peripheral Access Crates (PACs): Low-level register definitions.
  • Hardware Abstraction Layers (HALs): High-level, embedded-hal compliant APIs.
  • Wifi/Bluetooth Stacks: Support for both bare-metal (no_std) and Embedded Linux-like (std) environments.

This layered approach allows you to choose the level of abstraction that fits your specific edge use case from low-power sensor sleepers to high-throughput AI gateways.

Concept Match

Match Rust Ecosystem & Syntax

Drag each definition into its matching concept slot, then click Submit. Tap × to return a placed card to the pool.

Result and Option
drag a definition here…
TOML
drag a definition here…
Type Safety
drag a definition here…
embedded-hal
drag a definition here…
serde
drag a definition here…

Definition Pool

Types used for explicit error handling, providing a safer alternative to exceptions.
A framework for serialising data with zero runtime overhead using compile-time code generation.
A human-friendly configuration format used by Cargo for project metadata and dependencies.
A set of traits that allow drivers to be portable across different hardware platforms.
Ensures data integrity by preventing silent type conversions or precision loss.
  1. https://msrc.microsoft.com/blog/2019/07/a-proactive-approach-to-more-secure-code/