The Rust programming language was developed by Mozilla with the aim of creating a better tool for developing their browser Mozilla Firefox. However, the language appeared to be so effective, that many programmers are now opting to use it for software development instead of C++. Rust is syntactically similar to C++, but it provides increased speed and better memory safety.

In order to explain why Rust is a safer and faster language than C++, we decided to create a Rust vs C++ comparison chart that clearly shows the differences between these two languages. This article will be useful for people who are familiar with C++ but are new to Rust.

 

Written by:

Alexey Lozovsky,

Software Designer in System Programming Team

 

For better comparison, we’ve chosen features that reveal the key similarities and differences between these two languages.

  • Zero-cost abstraction
Issue C++ Rust
Preferring code duplication to abstraction due to high cost of virtual method calls Zero-cost abstraction mechanisms allow you to avoid runtime costs when possible. Zero-cost abstraction mechanisms allow you to avoid runtime costs when possible.
  • Move semantics
Issue C++ Rust
Move constructors may leave objects in invalid and unspecified states and cause use-after-move errors  Move constructors are suggested to leave the source object in a valid state (yet the object shouldn’t be used in correct programs). A built-in static analyzer disallows use of objects after they have been moved.
Use-after-move errors are detected at runtime using a special sentinel state. The compiler can rely on this built-in analyzer for optimization.
External static code analyzers can spot use-after-move errors at compile time.

 Smart pointers vs. null pointers

Issue C++ Rust
Use-after-free, double-free bugs, dangling pointers Smart pointers and references are preferred to raw pointers. Smart pointers and references are preferred to raw pointers.
Manual code review can spot use of raw pointers where smart pointers would suffice. Raw pointers can only be used inside unsafe blocks, which can automatically be found by tools.
Null dereferencing errors References are preferred to pointers and cannot be null. References are preferred to pointers and cannot be null.
Null dereferencing is still possible even for smart pointers, but is declared as undefined behavior and should never appear. Null references can be emulated by Option types, which require explicit null checks before use.
Compilers assume that undefined behavior never happens, don’t produce warnings, and use this for optimization (sometimes with fatal consequences for security). Smart pointers return Optional references and therefore require explicit checks as well.
External static code analyzers can spot possible errors at compile time. Raw pointers can be null, but they can only be used inside unsafe blocks. Unsafe blocks need to be carefully reviewed, but they can be found and marked automatically.
  • Internal buffer
Issue C++ Rust
Buffer overflow errors Explicitly coded wrapper classes enforce range checks. All slice types enforce runtime range checks.
Debugging builds of the STL can perform range checks in standard containers. Range checks are avoided by most common idioms (e.g. range-based for iterators).
  • Data races
Issue C++ Rust
Data races (unsafe concurrent modification of data) Good programming discipline, knowledge, and careful review are required to avoid concurrency errors. The built-in borrow checker and Rust reference model detect and prohibit possible data races at compile time.
External static code analyzers can spot some errors at compile time. A novel locking API makes it impossible to misuse mutexes unsafely (though still allowing incorrect usage).
External code sanitizers can spot some errors at runtime.
  • Object initialization
Issue C++ Rust
Uninitialized variables Constructors of user-defined types are recommended to initialize all object fields. All variables must be explicitly initialized before use (checked by the compiler).
Primitive types still have undefined values when not initialized explicitly. All types have defined default values that can be chosen instead of explicit initialization types.
External static code analyzers can spot uninitialized variables.
  • Static (compile-time) polymorphism
Issue C++ Rust
Static interfaces for static polymorphism Concepts should provide this feature directly, but they’ve been in development since 2015 and are only scheduled for standardization in C++20. Traits provide a unified way of specifying both static and dynamic interfaces.
Virtual functions and abstract classes may be used to declare interfaces. Static polymorphism is guaranteed to be resolved at compile time.
Virtual function calls may be optimized by particular compilers in known cases.
  • Adding new traits
Issue C++ Rust
Extending externally defined classes with new methods Adding new methods normally requires inheritance, which can be inconvenient. Traits can be added to and implemented for any class in any module at a later point.
Unified function call syntax could be used to emulate extension methods (if it gets into C++20). Modules restrict visibility of available methods.
  • Standard library
Issue C++ Rust
Legacy design of utility types heavily used by standard library Structured types like std::pair, std::tuple and std::variant can replace ad-hoc structures. Built-in composable structured types: tuples, structures, enumerations.
These types have inconvenient interfaces (though C++17 improves this). Pattern matching allows convenient use of structured types like tuples and enumerations.
Most of the standard library doesn’t use structured types. The standard library fully embraces available pattern matching to provide easy-to-use interfaces.
  • Branches in switch statements
Issue C++ Rust
Forgetting to handle all possible branches in switch statements Code review and external static code analyzers can spot switch statements that don’t cover all possible branches. The compiler checks that match expressions explicitly handle all possible values for an expression.
  • Typing of variables
Issue C++ Rust
Complex variable types become tedious to type manually The auto and decltype keywords provide limited type inference (for expressions). Local type inference (for a function body) allows you to explicitly specify types less frequently.
Lambda functions still require manual type specifications, but this is improving with C++17. Function declarations still require explicit types which ensures good readability of code.
  • Runtime environment
Issue C++ Rust
Embedded and bare-metal programming have high restrictions on runtime environment The C++ runtime is already fairly minimal, as it directly compiles to machine code and doesn’t use garbage collection. The Rust runtime is already fairly minimal as it directly compiles to machine code and doesn’t use garbage collection.
C++ programs can be built without the standard library with disabled exceptions and dynamic type information, etc. Rust programs can be built without the standard library with disabled range checks, etc.
  • Using libraries written in other languages
Issue C++ Rust
Using existing libraries written in C and other languages C libraries are immediately usable by C++ programs. C libraries require Rust-specific header declarations.
Libraries in languages other than C++ require wrappers. Libraries in languages other than Rust require wrappers.
Exporting a C interface requires only a simple extern declaration. Exporting a C interface requires only a simple extern declaration.
There’s no overhead in calling C functions from C++ or calling C++ functions from C. There’s no overhead in calling C functions from Rust or calling Rust functions from C.

Conclusion

In this article, we compared Rust and C++. You have no doubt noticed that both languages use zero-cost abstractions and move semantics. They also both have smart pointers, no garbage collection, and other similarities.

In contrast to C++, Rust has a built-in static analyzer but no uninitialized variables.

Rust avoids possible data races, informs about undefined behavior, and allows null raw pointers inside unsafe blocks. The Rust language also has other distinctive features that allow programmers to achieve better safety and performance of their software. If you’re interested in the basics of Rust, check out our series of in-depth Rust tutorials starting from part 1 here.

This comparison chart is based on Apriorit’s experience programming in both C++ and Rust. If you want to know more about our experience in software development, feel free to contact our team.

Subscribe to updates