Both C++ and Rust are fast, powerful programming languages. Both have pros and cons. This blog is my comparison and opinion of both programming languages by intermediate C, C++ and beginner Rust programmer.
Note:
I also code in TypeScript, JavaScript, Snail (Python) and couple other
Opinion
In my opinion with Modern C++ you may not need Rust. Why?
Short answer:
C++ has concept known as 'smart pointers', with them, you can easily imitate Rust's borrowing system mainly with unique_ptr
The long answer
It's not simple as unique_ptr
, unique smart pointer has an issue and that is it can point to the same memory only once. That's why there is shared_ptr
an another smart pointer that keeps track of how many of shared smart pointers are pointing to the same object in memory.
In rust
fn main() {
let mut x = 4;
let mut y = &mut x;
let mut z = &mut x; // Cannot borrow more than once
}
is the same as
#include <memory>
int main() {
auto x = std::make_shared<int>(4);
auto y = x;
auto z = x;
return 0;
}
In C++
However there is one issue. In rust, you can't borrow a variable as mutable more than one time. This is where C++ kind of wins, because you aren't limited to that restriction. However this gives you more boilerplate when getting the value out of the smart pointer.
Another example. What if we wanted to do something like
fn edit(val: &mut Vec<i32>) {
val.push(7);
}
fn main() {
let mut x = vec![4];
// [4]
edit(&mut x);
// [4, 7]
}
Well, very simply and this time let's use unique smart pointer
#include <vector>
#include <memory>
void edit(std::unique_ptr<std::vector<int>>& val) {
val->push_back(7);
}
int main() {
auto x = std::make_unique<std::vector<int>>();
x->push_back(4);
edit(x);
return 0;
}
This is one of couple possible solutions, you don't even need smart pointer for this, just make stack allocated object of vector and pass it as reference.
Now let's say you want to move it and make the function own it. In C++ use std::move
function, simple!
Rust
fn eat(mut val: Vec<i32>) {
val.push(7);
}
fn main() {
let mut x = vec![4];
eat(x);
println!("{:?}", x); // X inaccesible
}
C++
#include <cstdio>
#include <vector>
#include <memory>
void eat(std::unique_ptr<std::vector<int>> val) {
val->push_back(7);
}
int main() {
auto x = std::make_unique<std::vector<int>>();
x->push_back(4);
eat(std::move(x));
printf("%ld\n", x->size()); // x becomes nullptr
return 0;
}
This is where magic of Rust compiler comes to play, It won't compile saying that the value x
was moved. C++ compiler will compile this however the application will crash because x
will be turned into nullptr
yikes. You can check it either comparing it with nullptr
or switching to shared_ptr
and making weak_ptr
and calling expired
on the weak smart pointer. Like so
std::weak_ptr<std::vector<int>> hm = x;
eat(std::move(x));
printf("%d\n", hm.expired());
But you shouldn't move shared_ptr
🙂
Don't mimic Rust's borrowing system, you'll be regretting it.
And we're at the end. I know i left out so many features of Rust but all can be done in C++ as well.
I showed you some examples on how to make your C++ safe as Rust can but with more boilerplate and complex overhead. I hope you liked the blog post! Let me know by leaving a comment 💖 As a bonus, i'll write some C++ memory safety tips
C++ memory safety tips
-
new
should not be used and is dangerous, because on some platformsnew
returns zero or throws an exception and that will turn into pile of problems. To avoid that, includenew
header like this#include <new>
and change allnew
calls intonew(std::nothrow) ...
. This way, ifnew
fails, it will returnnullptr
. - Switching from pointers to smart pointers takes a LOT of time and debugging and everything, so you can simply secure your current
new
calls, follow tip number 1 - When allocating array with
new
, to free every single value from the array, usedelete[]
, notdelete
. With onlydelete
you'll free only the first element of the array.
Top comments (3)
I prefer C++ and keep simple rules:
new
operator;Declare static singletons for global objects instead of initializing via
new
operator and declare them in a static methods instead of declaring in.cpp
like a variable. Something like:Mainly use
new
operator, if:Such simple rules minimize memory risks. And use smart pointers only if indeed need.
I don't use smart pointers that much either. I do prefer the stack too - it's the fastest but sometimes, I do need to use pointers, for example when I deal with C functions or couple of other reasons. Being simple is always better and being safe is another plus.
One area Rust does shine is in synchronized access to objects. In C++ one can have an object, a lock, and a guard, with no real association between these elements, so you can still access the object even without acquiring a lock. In Rust the mutex is part of the object, and the guard is also used as an accessor to the content, so the lifetime of the lock and object access are the same.
This is actually relatively easy to simulate in C++ for objects and containers with simple templating. The guard object then also becomes a kind of weak pointer and the object your locking becomes a private member along with the sync object so it can only be accessed thru an active guard pointer. As I have found it such a useful pattern I incorporated it as a simple header-only template in my latest moderncli library release.