Hacker News new | past | comments | ask | show | jobs | submit login

I also use Rust now. If you use unsafe, there is little you can do in C++ that you can't do in Rust. However, some things like data races and mutable aliasing are permitted in C++, but not in unsafe Rust, as they invoke undefined behavior. The Rust nomicon has more details about what's allowed.



> However, some things like data races and mutable aliasing are permitted in C++, but not in unsafe Rust, as they invoke undefined behavior.

Define 'permitteed'. Unsafe is, among other things, exactly that. It tells rustc to permit you to do things that are ... well, unsafe. Including the ones you listed.

Maybe you don't understand the meaning of unsafe in Rust?

Unsafe causing UB is a possibility. As is taking two &mut refs to the same memory location using exactly that keyword. And that example works for multi-threaded Rust code using a shared resource accessed as such too.


The compiler is free to never write out your changes to memory in that shared resource use-case, which you might be depending on. AFAIK rust doesn’t have a standardized memory model which should be needed to write correct code that data races.


This isn't a difference between Rust and C++; data races are undefined behavior in both languages. If you want two different threads to be allowed to access the same memory without locking, that's what std::atomic (in C++) or std::sync::atomic (in Rust) does.


He’s talking about formal memory models, which are a bit different from what you’re talking about (though you’re correct that they are pragmatically similar.) C/C++ has a formalized memory model that describe the high level logical rules/guarantees for writing safe concurrent code, while Unsafe Rust doesn’t have one explicitly written in spec/paper (although they kinda borrowed a less-formalized version of it from C/C++.) It has to do with ironing out hardware-specific behavior to obey a certain set of rules, while making sure all the compiler optimizations do not perform any invalid transformations against these rules. (The Rustonomicon explains this well in layman terms, and acknowledges the complexity of the issue: https://doc.rust-lang.org/nomicon/atomics.html)

In practice, it seems like the C/C++ model has some glaring flaws anyway, so I can’t say that Rust’s is really “worse”. But since Rust has this mission to be better than C++ in terms of safety, this is one of thorny issues of Rust that need to be tackled to really let its advantages shine.


I thought the C11 memory model was considered correct, possibly modulo 'consume' which seems to be ignored. The implementation in terms of types instead of operations has a performance cost but otherwise works. What glaring flaws do you have in mind?


Well, not exactly “glaring” (unless you are a PL expert), but for instance there’s a paper that was referenced in that Rustonomicon link:

https://fzn.fr/readings/c11comp.pdf

And also

https://plv.mpi-sws.org/scfix/paper.pdf

Though maybe these issues were fixed in C++20 (which I only learned till recently that they’ve revised their memory model there!)


Nice references. Going to take me a while to make sense of them. A superficial interpretation is one says relaxed doesn't work and the other says sequentially consistent doesn't work. Not great news tbh, hopefully it'll look better on closer examination.


It's undefined behavior. You can write unsafe code that's UB in both Rust and C++ (it's permitted, which I think is your objection), but those are not correct programs and they may behave in surprising ways or break when seemingly unrelated changes are made.


if you're using unsafe then what's the argument to use rust? the comparison should be what you can't do in rust without unsafe.


Perhaps you can write a small subset of your program in unsafe Rust, like performance critical inner loop, and then the rest of the program in safe Rust.


What data races are permitted in C++?


Make some mutable objects, lets say a std::vector of Geese. Spin up two threads, A and B. Make a pointer, or a reference, or by whatever means you prefer, like an index, the same Goose from the vector, and give both A and B that pointer/ reference/ index whatever.

Both threads can now change the Goose at the same time. That's a data race. As a result it destroys Sequential Consistency, but that barely matters in C++ because it also is Undefined Behaviour, your program no longer has any meaning.

In (safe) Rust it won't let you give both A and B a way to mutate the same Goose at the same time, thus data races can't occur, thus you have Sequential Consistency, and your program has a defined meaning.


Yeah, although I wouldn't exactly call it "permitted" if it's undefined behavior.


C++ has this IFNDR - "Ill-Formed: No Diagnostic Required" in the standard which marks various things as simultaneously not C++ and so the standard doesn't define what should happen, and yet also your standards conforming compiler is not required to diagnose this and tell you about the problem.

You can presumably argue that all those things aren't "permitted" but there is no way to detect for sure if you wrote any of them, and if you did your entire C++ program has no defined meaning and might do anything at all.

There's a reason this exists. Rice's Theorem says non-trivial Semantic questions are Undecidable. You thus can't always correctly decide whether a program has some non-trivial semantic property, but C++ wants to require a whole lot of such properties. So, when it's difficult they just say the compiler must err on the side of emitting nonsense programs.

[Rust takes the other path here, if the Rust compiler can't be sure that your program has the required semantic properties you get an error. This is rare but annoying, typically you can easily modify your program to satisfy the compiler or when you try to do so you realise actually the compiler was right, this program doesn't have the required semantic properties]

I am increasingly confident that C++ made the wrong choice here, neither of these outcomes is desirable but the Rust outcome has a negative feedback loop - if changes make Rust's compiler annoy more programmers with spurious errors there's pushback. The C++ approach has positive feedback, as C++ gets less well-defined people's compilers accept whatever nonsense they wrote, nobody tells the committee to stop doing this - until one day it blows up in their face.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: