Rust <> C++

Topics:
- Converting C++ to Rust via @DavidHenderson
Converting C++ to Rust via @DavidHenderson
Slides: converting_cpp_to_rust.pdf
Converting C++ to Rust: Summary
Why Convert to Rust?
The primary driver for converting from C++ to Rust is memory safety. This has become a critical priority, with the US government actively advocating for memory-safe languages. Key benefits include:
- Memory safety guarantees - Safe Rust eliminates undefined behavior
- No garbage collector - Unlike other memory-safe languages, Rust provides safety without GC pauses
- Data race prevention - Particularly valuable for multithreaded applications
- Industry momentum - Major companies are transitioning: Microsoft Distinguished Engineer Galen Hunt has stated the goal to eliminate all C and C++ code from Microsoft by 2030, using AI and algorithmic assistance for translation
Safety-Critical Systems
Ferrocene, the qualified Rust compiler toolchain, meets stringent safety standards:
- ISO 26262 (ASIL D for automotive)
- IEC 61508 compliance
- Suitable for ADAS (Advanced Driver-Assistance Systems) and other safety-critical applications
Conversion Approaches
1. Total Rewrite ("Big Bang")
- Start from scratch in Rust
- Cons: Long development time before having testable code; risk of "second system effect" (over-engineering with new language features)
2. Automatic Conversion Tools
C2Rust (https://c2rust.com/)
- Translates C99-compliant C code to semantically equivalent Rust
- Provides translator, refactoring tools, and cross-checking utilities
- Pros: Good starting point
- Cons: C-only (not C++), produces unsafe and non-idiomatic Rust code, may carry over bugs from original code
bindgen (FFI approach)
- Generates Foreign Function Interfaces from C/C++ headers
- Creates Rust crate that calls C/C++ code
- Common pattern: "sys-" prefixed crates for bindgen wrappers, with clean Rust API built on top
- Typically used in build scripts
- Pros: Enables piecewise conversion, heavily used in practice
- Cons: Produces unsafe, non-idiomatic code
cbindgen
- Reverse direction: produces C headers from Rust code
- Allows calling Rust from existing C code
- Good for introducing Rust early in conversion
3. Gradual Introduction (Recommended)
This incremental approach is recommended for both schedule and cost reasons:
- Always maintain a working codebase
- Convert critical sections first
- Make use of Rust's built-in test framework
- Can result in either pure Rust or pragmatic C++/Rust hybrid
Gradual Conversion Process
Planning Phase
-
Understand the existing C++ codebase
- Review algorithms and data structures
- For embedded systems, identify platform-specific elements:
- Inline assembler code
- Hardware-specific drivers and low-level interfaces
- Direct memory and device access
-
Prioritize conversion targets
- Security-critical functionality
- Foundational modules, libraries, and frameworks
- Drivers and interfaces requiring heavy modification
Implementation Best Practices
-
Incremental rewriting along clear boundaries
-
Continuous testing:
- Compare byte-for-byte results between old and new implementations
- Use "Memory Access" trait to examine memory modifications
- Add tests for newly discovered bugs (porting may reveal latent defects)
- Integrate into CI/CD workflow
-
Performance monitoring (TPMs - Technical Performance Measures):
- Add instrumentation for memory usage, execution time, and image size
- Periodically compare against original C++ metrics
- Focus optimization efforts where needed
- Set up profiling with automated regression testing
Language Comparison Highlights
Structure
- C++: Namespaces, #include, separate header and source files, CMake/make
- Rust: Modules (just name the file), .rs files serve as modules, cargo build system
Key Feature Differences
| C++ | Rust | Notes |
|---|---|---|
Preprocessor directives (#define, #ifdef) | Attributes (#[test], #![feature]) | Used for traits, linting, conditional compilation |
| Various cast operators | as, Into<>, From<> traits | Rust disallows C++-style casting |
std::vector, std::map, etc. | Vec, HashMap, HashSet | Similar functionality |
enum, union | enum, union (unsafe) | Rust enums are more powerful and safer |
| try/catch, error codes | Result<T, E>, Option, panic! | More explicit error handling |
switch | match | More flexible, always requires default handler |
Pointers (char *) | Raw pointers, references | Raw pointers are unsafe |
String Handling
- C++:
char*,std::string,wchar_t* - Rust:
str,String,OSString(for FFI),OSStr - Rust uses UTF-8 encoding by default
Embedded Systems Considerations
Memory Management
- C++ embedded typically uses preallocated memory blocks (not preferred by the presenter)
- Must allocate all memory at boot and carefully share buffers
- Rust avoids garbage collection pauses while maintaining safety
Threading Model
- Rust embedded often models interrupts as threaded code
- Interrupt context treated as a thread with mutex switching
- Hardware interrupts can occur millions of times during execution
FFI Patterns
- Use
#[no_mangle]attribute to maintain function names in linker (good for porting) - Common pattern: sys-prefixed bindgen crates with cleaner Rust wrapper on top
- Some C++ code may remain indefinitely (like kernel code)
Tools and Resources
Rust Embedded Ecosystem
- Embassy (https://embassy.dev/) - Framework for embedded applications, provides generalized HAL
- c-types crate (https://crates.io/crates/rs-ctypes) - Provides C type definitions
- cargo-make - Make-like functionality integrated with cargo.toml
Community Resources
- Embedded Rust Matrix channel: https://matrix.to/#/#rust-embedded:matrix.org (where hardware people gather)
- Embedded Working Group: https://github.com/rust-embedded/wg
- Embedded Working Group Blog: https://blog.rust-embedded.org/
- The Rustonomicon: https://doc.rust-lang.org/stable/nomicon/ (guide to unsafe Rust)
Additional References
- Official Rust documentation and installation guides
- GitHub: locka99/cpp-to-rust-book
- Various practical porting guides available on Medium and elsewhere
Key Takeaways
- Memory safety is the compelling reason to switch, not necessarily productivity gains
- Gradual, incremental conversion is the most practical approach
- Maintain continuous testing and performance monitoring throughout conversion
- Use FFI tools (bindgen/cbindgen) to enable piecewise migration
- Accept that some code may remain in C++ indefinitely (pragmatism applies)
- Avoid over-optimization early; collect metrics to guide focused optimization efforts
- Be aware of the "second system effect" - resist the urge to over-engineer with new language features
Crates you should know
- https://crates.io/crates/hickory-dns: Hickory DNS is a safe and secure DNS server with DNSSEC support
- https://crates.io/crates/rstar: A flexible, n-dimensional r*-tree implementation for the Rust ecosystem, suitable for use as a spatial index.