The magnitude matters.
1) I have a more ergonomic/precise/local contract to satisfy safety
2) Since this unsafe block is local, it is easier to set up its testing conditions for various scenarios. Otherwise, testing for bigger unsafe block (e.g. unsafe language) would also have to handle coupling between api from which ub originates and the rest of the code.
At the end of the day this isn’t a technical argument I’m trying to make, it’s a philosophical one. I think that the more we normalize eroding the core benefits the language safety features provide, one enhancement proposal at a time, one escape hatch added each year for special interfaces, the less implicit trust you can have in rust projects without reviewing them and their dependencies for correctness.
I think that trust has enormous value, and I think it would suck to lose it. (reflect: what does seeing “written in rust” as a suffix make you think about a project’s qualities before you ever read the code)
If one claims otherwise, I say they have no understanding of Rust. But also, if one helds that against Rust's value promise, I, again, say that they have no understanding of Rust.
That the community is going to be significantly more dramatic than average
In a more practical sense, all software, even Python programs, ultimately call C functions that are unsafe.
It's like that saying "all abstractions are wrong, some are useful".
> what does seeing “written in rust” as a suffix make you think about a project’s qualities before you ever read the code
By itself, that tells me very little about a project. Same thing if I see a project written in Python or Go, which are nominally memory safe programming languages. I perceive a statistically significant likelihood that software written in these languages will not segfault on me, but it's no guarantee. If I see two programs with the same functionality, where one is written in Python and another one in Rust, I also have some expectation that the one written in Rust will be more performant.
But you cannot draw general conclusions from that piece of information alone.
However, as a programmer, Rust is a tool that makes it easier for me to write code that will not segfault or cause data races.
- Memory leaks are not just possible in Rust, they're easy to write and mildly encouraged by the constraints the language places on you. IME I see more leaks in Rust in the wild than in C, C#, Python, C++, ...
- You can absolutely have data races in a colloquial sense in Rust, just not in the sense of the narrower definition they created to be able to say they don't have data races. An easy way to do so is choosing the wrong memory ordering for atomic loads and stores, including subtle issues like those arising from mixing `seq_cst` and `acquire`. I think those kinds of bugs are rare in the wild, but one project I inherited was riddled with data races in Safe rust.
- Unsafe is a kind of super-unsafe that's harder to write correctly than C or C++, limiting its utility as an escape hatch. It'll trigger undefined behavior in surprising ways if you don't adhere to a long list of rules in your unsafe code blocks (in a way which safe code can detect). The list changes between Rust versions, requiring re-audits. Some algorithms (especially multi-threaded ones) simply can't even be written in small, easily verifiable unsafe blocks without causing UB. The unsafeness colors surrounding code.
The Rustonomicon [1] serves as a decent introduction to what you can or can't do in unsafe code, and none of that changed to my knowledge.
I agree that it's sometimes challenging to contain `unsafe` in a small blast zone, but it's pretty rare IME.
At a minimum you have to check that the rules haven't changed for each version [0].
The issue with destructors just before 1.0 dropped [1] would have been something to scrutinize pretty closely. I'm not aware of any major changes since then which would affect previously audited code, but new code for new Rust versions (e.g., when SIMD stabilized) needs to be considered with new rules as well.
> none of that changed to my knowledge
This is perhaps a bit pedantic, but the nomicon has bug fixes all the time (though the underlying UB scenarios in the compiler remain stable), and it's definitely worth re-examining your unsafe Rust when you see changes which might have incorrectly led a programmer to write some UB.
[0] https://doc.rust-lang.org/reference/behavior-considered-unde... [1] https://cglab.ca/~abeinges/blah/everyone-poops/
That I only remember such things vaguely and not in a “oh yeah here’s the last ten times this happened and here’s the specifics” speaks to how often it happens, which is not often.
Lots of times soundness fixes are found by people looking for them, not for code in the wild. Fixing cve-rs will mean a “breaking” change in the literal sense that that code will no longer compile, but outside of that example, no known code in the wild triggers that bug, so nobody will notice the breakage.
Such as?
> everything that is exaggerated is insignificant
But are the non-exaggerated things significant?
Perhaps, but data here would be nice. Yes, Rust has Rc/Arc and whatnot. If we're talking anecdotes, I mostly see "we rewrote it in Rust and it uses less memory".
> You can absolutely have data races in a colloquial sense in Rust, just not in the sense of the narrower definition they created to be able to say they don't have data races...
Sure, although it's not a contrived Rust definition. Race conditions are far more general and harder to prevent. Race conditions often arise in higher levels of abstraction, so it's not so much Rust's focus. I don't know what to say about your comment on atomics except that Rust isn't making them much harder than C++ is.
> Unsafe is a kind of super-unsafe that's harder to write correctly than C or C++, limiting its utility as an escape hatch...
This is definitely important for anyone considering or learning Rust to know. However, how often is this a problem compared to what would be done in C or C++? Someone upthread mentioned enhanced greppability/auditability, Rust has much stronger prevention of memory corruption by default (even if unsafe can poke holes), and the culture is generally more averse to "unsafe". This seems more like a theoretical concern with little practical grounding. I highly doubt this is a real problem.
Exactly this, and very well put!
I'd just like to add one small but important detail. It's one of the things that is so obvious to one group that they rarely even mention it, but at the same time so obscure to the others that they are completely oblivious to it.
While the unsafe code is cordoned off into a corner its effects are not. A bug in an unsafe block in one part of your program can trigger an outcome in a completely different and safe part of your program, that normally safe Rust should prevent.
To put it more metaphorically, Rust restricts the places where bombs can be placed, it does not limit the blast radius in case a bomb goes off.
This is still huge progress compared to C/C++, where the bombs can and usually are everywhere and trying to write it safely feels a lot like playing minesweeper.
It is categorically the fault of that unsafe code when the bomb goes off. In a language like C++ it is very tempting for the person who planted the bomb to say "Oh, actually in paragraph sixteen of the documentation it does tell you about the bomb so it's not my fault" but nobody reads documentation, so Rust culturally requires that they mark the function unsafe, which is one last reminder to go read that documentation if you must use it.
Because this is a matter of culture not technology we can expect further refinement both in terms of what the rules are exactly and the needed technology to deliver that. Rust 1.82 which shipped yesterday adds unsafe extern (previously all the extern functions were unsafe, but er, maybe we should flag the whole block? This will become usual going foward) and unsafe attributes (the attributes which meddle with linking are not safe to just sprinkle on things for example, again this will become usual for those attributes)
The same algorithms already become ugly/obfuscated in idiomatic C++.
Looking at the macro in the LWN article, the approach of Rust of using wrappers and boxes and complex macros to emulate features appears to go into the same direction as C++.
Still in 2024, gdb is far less useful for C++ than for C. C++ error messages are far less useful.
All of that matters for reliable software, crashes (which can occur anyway with unsafe) are just a tiny part of the equation.
In all 3, you gotta verify [frama-C, astree,bedrock, the many projects working on rust, esp the coq one] and extensively test it.
But by default, all 3 provide a different level of gurantees
In C boilerplate is a called pre-processor magic.
As an OS nerd, this is what I like to use as an example: yes, the seL4 verified microkernel is impressive and if it was written in a language other than C, it wouldn't have both the practicality and the assurance. It was specified in Haskell but ultimately the C is what is deployed, so C it is. A Rust version might not be verifiable even in the next ten years. But the people who can't use seL4 and need an 80% "reasonably secure" or whatever OS have a strong case to use Rust over C. The formal verification for the C code of seL4 is partly a crutch for C's lack of safety and correctness by default.
It's very useful indeed, I've been programming in Rust daily for the past 7 years (wow time flies) and the times when I've needed unsafe code can still be counted on my two hands.
Yeah. They can. You could even conceivably start a new c project with a "sufficiently right toolset" so that you get the same safety as rust...
Just, no one does.
For those projects that don't use any unsafe, we can say -- absent compiler bugs or type system unsoundness -- that there will be no memory leaks or data races or undefined behavior. That's useful! Very useful!
For projects that do need unsafe, that unsafe code can be cordoned off into a corner where it can be made as small as possible, and can be audited. The rest of the code base is just as safe as one with no unsafe at all. This is also very useful!
Now, sure, if most projects needed to use unsafe, and/or if most projects had to write a significant amount of unsafe, then sure, I'd agree with you. But that's just not the reality for nearly all projects.
With C, everything is unsafe. Everything can have memory leaks or data races or undefined behavior. Audits for these issues need to examine every single line of code. Compilers and linters and sanitizers can help you here, but they can never be comprehensive or guarantee the absence of problems.
I've been writing C for more than 20 years now. I still write memory leaks. I still write NULL pointer dereferences. I still struggle sometimes to get my data ownership (and/or locking) right when I have to write multithreaded code. When I get to write Rust, I'm so happy that I don't have to worry about those things, or spend time with valgrind or ASAN or clang's scan-build to figure out what I've done wrong. Rust lets me focus more on what I actually care about, the actual code and algorithms and structure of my program.