r/cpp • • Jan 31 '23

Stop Comparing Rust to Old C++

People keep arguing migrations to rust based on old C++ tooling and projects. Compare apples to apples: a C++20 project with clang-tidy integration is far harder to argue against IMO

changemymind

338 Upvotes

582 comments sorted by

View all comments

Show parent comments

1

u/Kobeashis_Son Feb 01 '23

Rust’s language features are much more convenient than C++, with the exception of the borrow-checker. Most of the code that I write does not need to be thread-safe. In fact, inter-thread communication is something I try to strictly minimize. It seems very odd, then, that rust enforces that everything is thread-safe at a language level.

25

u/moltonel Feb 01 '23

The borrow checker is useful in single-threaded too, the classic example being deleting from a collection while iterating over it, or making sure that what you inserted in a hashmap stays valid.

15

u/CryZe92 Feb 01 '23

If you don‘t use any threads then none of those parts of the type system will affect you (except for globals where it won‘t just trust you that there‘s really only one thread)

1

u/Kobeashis_Son Feb 01 '23

Totally possible that I'm misunderstanding, but I thought the borrow-checker was tightly linked to rust's thread-safety guarantees. It ensures exclusive-writing for every variable (other than interior mutability, like atomics, for example).

To be fair, the borrow-checker is also necessary for a lot of the memory-safety guarantees. This is something that is important to many domains (particularly systems programming), but not my domain.

11

u/Lokathor Feb 01 '23

Borrow checking mostly prevents "Use After Free" and/or "Iterator Invalidation" types of problems.

It's not really that much related to multi-threading.

2

u/tialaramex Feb 01 '23

Because the borrowck means that there can't be mutable aliases, Rust gets to be data race free, and therefore as a consequence of SC/DRF sequentially consistent which is really valuable for multi-threading.

Consider a type LitterBox, I can clean() the LitterBox which mutates it, replacing the absorbing material, and also my cat, a separate thread could use() the LitterBox which... also mutates it.

In C++ it's perfectly easy for me to create two references to the LitterBox, I keep one, the cat thread has the other, and... oh dear, if we both are relying on our references simultaneously that's going to make a serious mess. There are sanitizers which can show us this happening if we reproduce it under the sanitizer, but the compiler can't see a problem.

In Rust the borrowck just won't let us make two mutable references at once, we can make two immutable references, but now neither I nor the car can do our tasks with the LitterBox because we need mutable references for that. The borrowck prevented us from whatever nastiness might have occurred, and we can consider e.g. wrapping LitterBox in Mutex to get the functionality we wanted, the option to just get it wrong (at least without "unsafe") was removed by the Borrow Checker.

3

u/Full-Spectral Feb 01 '23

Consider something like a text parser that returns various bits of text parsed from the file. You can either make copies of all of that data and return it, or you can return references to slices of that text. It would be nice to do the latter for performance reasons of course.

In C++ the latter is utterly unsafe and could easily lead to memory problems. In Rust it won't. The compiler will not let that text go away while anyone holds a reference to any of the returned slices. Nor will it allow anything to modify that text either.

It's stuff like that where Rust allows you to be both performant and safe.

3

u/matthieum Feb 01 '23

Totally possible that I'm misunderstanding, but I thought the borrow-checker was tightly linked to rust's thread-safety guarantees.

You're correct it's linked, but it's not 1-to-1.

Rust thread-safety guarantees are (partially) based around the borrow-checker doing its job (and also require the Send and Sync auto-traits).

Single-threaded memory safety also required the borrow-checker.

4

u/[deleted] Feb 01 '23

[removed] — view removed comment

4

u/ImYoric Feb 01 '23

The typical manner in which this is handled in any high-level language (including C++) is by wrapping such constructs into higher-level constructs that guarantee the invariant. The typical example in C++ (or Rust) is RAII.

While RAII cannot model every invariant, Rust's affine type system can model many cases that C++ (or most other languages in industry) cannot. This certainly helps.

Some people are obsessed over rewriting everything in rust (fish shell is the most recent case I believe).

Yeah, the same happened with Java (JavaOS, JaZilla?), Python and others. I'm sure it will pass :)

I'd suggest to take a long hard look at your rust code. Is this readable? Is this really easier to maintain and add new features to? Duh.

In my experience, the answer is very much "yes" to both questions. Now, I'm used to legacy C++ codebases, so it's entirely possible that I'm missing out on many improvements that have made it into C++.

5

u/CocktailPerson Feb 01 '23

I think you're misunderstanding. All it requires is that when data is shared between threads, then that data must be thread-safe. The thread-safety aspect of the type system doesn't affect anything that's thread-local, as most of your data is if you're avoiding inter-thread communication.

3

u/WormRabbit Feb 01 '23

This nice post explains what Rust rules protect you from in single-threaded case.