Software Defined Vehicles

Automotive Software Development

A Rusty New Car

4 min
Rust is similar in syntax to C and C++, offering comparable flexibility to these older languages but with significantly stronger safety guarantees.

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