Products

What makes Rust different from C++ for embedded engineers?

In the first article of this series, we explored why Rust is attracting attention in embedded software engineering. Safety, security, maintainability, and concurrency are compelling motivations, but they do not explain what it actually feels like to develop embedded software in Rust.

For experienced C and C++ developers, Rust is best understood not as a safer C++ but as a language that encourages different architectural choices. Ownership replaces many manually enforced memory conventions. Traits provide a different abstraction model than traditional interfaces. Composition is preferred over inheritance. And the compiler actively verifies many assumptions that are often left to coding guidelines, reviews, and testing.

The result is a language that feels familiar in capability but different in philosophy.

Rust changes the way you think about ownership

At the heart of Rust lies a simple but powerful concept: every value has an owner.

Ownership determines:

  • Who controls a resource
  • How long it lives
  • Whether it may be modified
  • Who may access it

For embedded engineers, this matters because many difficult defects originate from unclear ownership:

  • Use-after-free defects
  • Dangling pointers
  • Buffer lifetime issues
  • Resource management errors
  • Accidental sharing of mutable state

In safe Rust, ownership and borrowing rules are enforced by the compiler before the software ever runs. Questions that often remain implicit in C++ become explicit design decisions:

  • Who owns this resource?
  • Is this access shared or exclusive?
  • How long must this reference remain valid?

In many ways, Rust turns ownership conventions that embedded teams already follow into rules that the compiler can verify.

Traits are more than interfaces

Rust supports object-oriented design concepts such as encapsulation, abstraction, and polymorphism, but it does not provide classes.

Instead, Rust combines:

  • Structs for data
  • Impl blocks for behavior
  • Traits for abstraction
  • Generics for reuse

For C++ engineers, traits initially resemble interfaces:

1 trait Switchable { 2 fn switch_on(&mut self); 3 fn switch_off(&mut self); 4 }

The similarity is real, but incomplete.

A Rust trait combines aspects of:

  • A C++ interface
  • A C++20 concept
  • A template constraint
  • A runtime-polymorphic base class

The most important difference is that compile-time polymorphism is the default.

1 fn blink(led: &mut T)

This uses static dispatch. The compiler generates code specialized for the concrete type.

Runtime polymorphism is an explicit choice:

1 fn blink(led: &mut dyn Switchable)

Here Rust uses a vtable, similar to a virtual interface in C++.

This distinction is subtle but important:

  • In Rust, static polymorphism is the default. Runtime polymorphism is opt-in.

For embedded software, that aligns naturally with performance-sensitive and resource-constrained systems.

Safe concurrency is part of Rust’s design

Memory safety is only half of Rust’s story.

The other half is concurrency.

Modern embedded systems increasingly perform many activities simultaneously:

  • Networking
  • Diagnostics
  • Logging
  • Security monitoring
  • Sensor processing
  • Vehicle communication
  • Cloud connectivity

Historically, concurrency bugs have been difficult to diagnose because they often depend on timing and only appear under specific operating conditions.

Rust approaches this differently.

The same ownership and borrowing mechanisms that help manage memory also govern how data may be shared between execution contexts.

In safe Rust, data races are prevented by the type system.

This is one of the ideas behind Rust’s often-quoted term fearless concurrency.

However, Rust does not magically solve all concurrency problems, it does not automatically prevent:

  • Deadlocks
  • Priority inversion
  • Starvation
  • Scheduling mistakes
  • Incorrect synchronization strategies
  • Higher-level logic races

Rust helps eliminate an important class of concurrency defects, but sound concurrent design remains an engineering responsibility.

Async is becoming increasingly important in embedded systems

Embedded software is becoming more connected, distributed, and event-driven.

Today’s systems frequently need to handle:

  • Ethernet communication
  • OTA updates
  • Service-oriented architectures
  • Security services
  • Cloud integration
  • Diagnostics
  • User interaction

Traditionally, concurrency was achieved through threads, tasks, interrupts, or polling loops.

An increasingly popular alternative is asynchronous programming.

Instead of dedicating a thread to every activity, async systems allow many logical tasks to share execution resources efficiently.

Rust’s async model differs from many managed environments.

Async functions are compiled into state machines implementing the Future abstraction:

1     async fn receive_can_frame() {
2         ...
3     }

The compiler transforms this into native code capable of suspension and resumption.

Rust async does not require:

  • A garbage collector
  • A virtual machine
  • A managed runtime

However, async is not entirely free.

Futures still require an executor to make progress. On servers this might be Tokio; in embedded systems projects such as Embassy provide lightweight executors designed specifically for microcontrollers.

The key takeaway is not that async is universally better than tasks or threads. The key takeaway is that Rust integrates modern concurrency models into a systems language while maintaining explicit control over memory and resources.

Composition over inheritance

One of the largest philosophical differences from C++ is Rust’s approach to reuse.

Rust deliberately avoids classical implementation inheritance.

Instead, it favors composition.

1     struct BoardLed {
2         controller: LedController,
3         pin: OutputPin,
4     }

Rather than asking whether one type is a specialized version of another, Rust encourages engineers to assemble behavior from reusable building blocks.

Reuse is typically achieved through:

  • Composition
  • Traits
  • Generics
  • Default trait implementations

This often leads to simpler dependency structures and clearer ownership boundaries.

Embedded adoption is practical

The value of a language extends beyond its syntax.

Rust’s ecosystem significantly contributes to its growing adoption.

Key components include:

  • Cargo for build and dependency management
  • embedded-hal for portable hardware abstractions
  • Embassy for modern asynchronous embedded development

Just as importantly, Rust supports incremental adoption.

Most embedded organizations have substantial investments in C and C++ code. Through standard Foreign Function Interfaces (FFI), Rust can interoperate with C-compatible APIs, making gradual migration practical.

This means teams can introduce Rust selectively in areas where safety, security, concurrency, or maintainability concerns justify the investment.

Conclusion: Rust is as much about architecture as safety

Many engineers first investigate Rust for memory safety, but its deeper value is architectural clarity: ownership, dependencies, mutability, and concurrency become explicit design choices rather than hidden assumptions. That explicitness helps teams find defects earlier and reason about software structure more confidently. As embedded systems become more connected, asynchronous, multicore, and software-defined, Rust offers a disciplined way to express intent and constrain unsafe assumptions.

Further Reading

Next in this series

Rust and AUTOSAR: How Rust components can coexist with AUTOSAR architectures, where Rust is emerging within automotive software ecosystems, and how Rust’s ownership, safety, and concurrency model align with the industry’s growing focus on software-defined vehicles and cybersecurity.

Jan Richter

Ferenc Valenta

This article first appeared on the Siemens Digital Industries Software blog at https://blogs.sw.siemens.com/ee-systems/2026/09/29/what-makes-rust-different-from-c-for-embedded-engineers/