I share the sentiment, and have a very practical anecdote to share along the same lines.
One day at work, a coworker asked for help on a strange bug he was experiencing with a library in C++. The lib would return a list of values, but depending on the number of values it would be either correct, or a long string of garbage values.
Suspecting a memory error, I ran the code under valgrind (bless valgrind's devs, everyday) and sure enough, a memory error was there.
Using my Rust-sharpened borrow-checker sense, I could narrow it down to the list returned being a reference-like object (think
span) to a temporary that wouldn't live long enough. The fix was along the lines of:
// Before, with memory errors
auto list = some_query().get_results().get_list();
// After, fixed
auto results = some_query().get_results();
auto list_view = get_list();
The thing is, that particular lib is available both as C++ and rust (native in both cases, no bindings, they are 2 implementations of the same concept if you want).
Another day, I was implementing a module in Rust using the Rust version of that library for testing purposes. Accidentally, I wrote the same mistake as my colleague did in C++:
let list = some_query().results().list();
I was greeted with a
temporary value dropped while borrowed. Consider using a let binding to create a longer lived value. Temporary is freed at the end of this statement, borrow later used here error from the compiler.
This made me realize a few things very clearly:
- It is terrifyingly easy to introduce lifetime related errors in C++ code, and "modern C++" will definitely not save you from these (despite what is regularly heard on the topic)
- These errors are hard to spot in C++. It took 1 hour and a half of two engineers to understand and fix the problem. And this is when such mistakes are detected in the development phase (if my colleague hadn't had the reflex to test on various workloads we might have not detected the problem)
- By contrast, rust makes detecting and fixing these errors a triviality. The compiler even suggest what to do to fix the error. It should take no more than 10 seconds of a single developer to apply that fix in such a trivial situation.
- Rust trains developers to spot these kinds of errors, because they are reified by the compiler. After seeing lifetime related problems a lot of time in rust, and their fix as offered by the compiler, I tend to detect these more easily when reviewing C++ code, because I'm always thinking about the lifetime of objects at the back of my mind. My colleague, a very experienced C++ developer, but less versed in rust, has more trouble detecting this kind of issues.
- Even once we know of this kind of issues, it is very easy to write them by mistake. I don't know if the lib is to blame, but the combination of generally long method chains whose intermediate results we are not interested in, and maybe their naming (adding
list_ref() instead of just
list() would make things easier?) makes it so we tend to do that mistake again and again. As a matter of fact, after having fixed it by my colleagues and made it myself in rust, I wrote it a second time in C++... Thankfully it was detected quickly.
Nowadays, when I write something non trivial using that lib, I tend to write it in rust first, just to check my lifetimes, and then port these parts to C++ as needed .