Automotive Software Development
A Rusty New Car
Rust is similar in syntax to C and C++, offering comparable flexibility to these older languages but with significantly stronger safety guarantees.
mirsad - stock.adobe.com
The relatively new programming language Rust promises safety, but risks still lurk under the hood. In the automotive industry, even the smallest error can be fatal – and yet C/C++ remain in play. A risky mix with consequences.
The relatively new programming language,
Rust, is similar to C and C++ in syntax, provides much of the flexibility of those
older languages but with much stronger safety guarantees that overcomes many of
the memory-related problems encountered by C and C++ programmers and users. Rust’s
expressive syntax, strong type system, and helpful compiler error messages significantly
reduces the likelihood bugs being introduced. This is essential for embedded
automotive systems that operate under strict resource constraints and real-time
requirements.
Rust is therefore emerging as a alternative
choice for the development of new software modules in high-criticality systems
such as those found in the automotive industry. That’s why many major automotive manufacturers
are working with Rust to develop prototypes.
Providing memory-safer structures
Rust users avoid many memory-safety issues
by taking advantage of its strict rules and built-in support for memory
allocation. For example, compile-time checks help guarantee the correct
behaviour of references. Rust also has datatypes that provide pointer-like
features that are supported by those compile-time checks. These checks help
prevent issues typically encountered with C-like pointers.
By providing memory-safer structures and
manipulation techniques, Rust can speed up the development and testing of automotive
software. It also leverages the contemporary skills that developers are now learning
and being trained in. These two factors are important to the automotive industry
due to the fact that the complexity and volume of software systems being
integrated into vehicles of every type is growing rapidly and is unlikely to
slow.
However, just because Rust makes it more
difficult to inadvertently introduce safety and security flaws, including
memory faults, it does not mean that Rust-based codes will always be exempt
from these runtime errors. In fact, these errors can still make their way into
Rust software in a few ways.
Strict compilation checks
First of all, a Rust program may inherit a
runtime error by calling code developed in C or C++. Many of the OEMs and Tier
1 automotive manufacturers that are exploring Rust are doing so by gradually integrating
aspects of Rust alongside legacy code to mitigate risks as well as introduce
Rust’s benefits. These automotive
companies have invested heavily in C and C++ coding and toolchains for decades,
so they are understandably trying to reuse their existing code bases. They may
also be reluctant to plunge headfirst into Rust, particularly when it comes to
safety. So, all of the software code,
existing and new combined, still needs to be verified once integrated into a system
that includes large portions written in C, C++, Rust or an additional language.
Second, there are needs that can only be
met by bypassing some of Rust’s safeguards. This is particularly true for
embedded control systems. Many low-level interactions such as accesses to
memory-mapped hardware registers or data buffers, cannot easily be performed
under Rust’s strict compilation checks.
Code that dereferences a raw pointer in
Rust, for example, will trigger a compile error unless developers encapsulate
that code within unsafe {} blocks. This marking tells the compiler to not
perform its usual safety checks when compiling this code. As the result, the
chances that a memory issues slips through to production code increases.
The role of unsafe blocks
There are other situations where developers
need to use unsafe {} blocks, such as usage of unions. Although unions are not a core Rust feature,
support for unions can be important to ensure Rust’s compatibility with C and
C++. That’s because the compiler cannot guarantee the safety of operations on
any fields because it cannot determine whether writes to one field will or will
not corrupt the other fields that share the same memory structure.
There are many situations where unsafe
markings are required in native Rust code. Within the general-purpose library
that Rust uses, roughly 30% of the packages within the collection at crates.io
use the unsafe{} construct. Again, a compiler cannot check operational safety
within these packages.
Finally, to make matters worse, there are
also situations where code that is not highlighted as “unsafe” in Rust
can still experience memory issues at runtime. The Rust Book (the Rust language
official documentation) even lists some trivial examples of these, such as an
out-of-bounds access. Such codes will compile, however if specific combination
of values are experienced at runtime, the error will cause the Rust thread to
crash. Of course, such events are very
undesirable in safety-critical applications such as the ones we find in
Automotive.
However, just because runtime errors can
still make their way to Rust programs does not mean these cannot be
exhaustively detected before compilation. Formal verification and
mathematical-reasoning techniques exist that enable code analysis before execution.
They can determine whether code will suffer from memory-safety issues such as
buffer overflows, null-pointer accesses and other issues.
Reducing the risk of costly recalls
Such static-analysis techniques are
exhaustive in a way that provides guarantees at a level that even extensive
dynamic testing cannot. Proper implementation of these methods ensure zero
false negatives (failing to indicate the presence of a real error in code) and
a low level of false positives (indicating an error where none exists). Using
these techniques, developers working with
Rust-based can highlight where problems can occur, and can then provide
additional safeguards to ensure proper operation of the software. In general,
the automotive industry’s drive toward electrification and connectivity requires
a new approach to software development. Rust stands out as a language capable
of reducing the risk of costly recalls due to software errors; enabling secure
over-the-air updates and remote diagnostics; and modernising the skillset and
productivity of automotive software teams.
As more automotive organisations contribute
to the Rust ecosystem, the language is poised to play an increasingly important
role in the vehicles of tomorrow.
But even if Rust helps to reduce the
possibility that errors get inadvertently introduced in the code base, it does
not eliminate it. For starts, Rust will likely coexist with C and C++ for the
foreseeable future. And the fact is that even if Rust adopts a safer approach
to memory-related issues, such issues can still happen, even in code
bases that are entirely written in Rust and forgo the use of unsafe blocks.
The good news is that Rust-friendly tools like TrustInSoft’s TrustInSoft
Analyzer, a static analysis tool that uses Formal Methods to provide
mathematically proven guarantees about software safety and security, can be
used to pinpoint exhaustively all of these issues, independently whether they
are raised by Rust native code or calls to C/C++. Even better, TrustInSoft
Analyzer can detect these hard-to-find bugs directly in mixed code bases, which
increases the precision of its diagnostics, lowering the number of false
positives. By leveraging Formal Methods verification, TrustInSoft Analyzer reduces
the need for extensive manual testing while ensuring that the combined automotive
software codebase is free of unsafe memory issues long before it hits the road.
This article was first published at
all-electronics.de