7,581 karma · joined September 4, 2012
This is correct, and it's hard to teach, and I agree that a lot of folks get it wrong. (Here's my attempt: https://jacko.io/safety_and_soundness.html.) But I think this comment is understating how big of a difference this makes:
1. Rust has a large, powerful safe subset, which includes lots of real-world programs. Unsafe code is an advanced topic, and beginners don't need to learn about it to start getting their work done. Beginners can contribute to big projects without touching the unsafe parts (as you clarified, that means the module privacy boundaries that include unsafe code, not just the unsafe blocks), and reviewers don't need to be paranoid about every line.
2. A lot of real-world unsafe Rust is easy to audit, because you can grep for `unsafe` in a big codebase and zoom right to the parts you need to look at. Again, as you pointed out, those blocks might not be the whole story, and you do need to read what they're doing to see how much code they "infect". But an experienced Rust programmer can audit a well-written codebase in minutes. It's not always that smooth of course, but it's a totally different world that that's even possible.
In Rust you presumably do all of that with raw pointers, which leaves you with a big unsafe mess to clean up over time, and I imagine a lot of the hard work of this project is trying to minimize that mess. But at least the mess that you have is recognizably Rust, and incremental cleanup is possible.
1. Work at a company that runs some ancient version of CentOS or something.
2. The system package manager doesn't have anything newer than Python 3.6.
3. Official binaries from python.org print an inscrutable error about the glibc version when you try to run them.
4. Building Python from source requires some new system dependencies, takes forever, and then produces an even less scrutable error about a missing header file.
5. Googling that error leads to a ./configure flag that works around the issue, which upstream GCC fixed in 2017.
6. Success?
If you haven't seen that error #3 before, or dealt with "manylinux" build environments, you've dodged an entire world of pain. The same goes for scripting installs on Windows, and for the part of that page that's talking about "limiting the CPU instructions that can be used" :')
The "blue can't call red" property -- the plot climax of the original function coloring article -- only applies to JS. Other languages can call async functions from sync functions using some sort of "block on" or "wait for" API. The function coloring problem is still annoying, but it's not a fundamental limitation like it is in JS.
Most of the security bugs we hear about don't cause random crashes on otherwise healthy machines, because that tends to get them noticed and fixed. It's the ones that require complicated steps to trigger that are really scary. When I look at NTP, I see a service that:
- runs as root
- talks to the network
- doesn't usually authenticate its traffic
- uses a bespoke binary packet format
- almost all network security depends on (for checking cert expiration)
That looks to me like an excellent candidate for a memory-safe reimplementation.
- Zig's comptime is extremely flexible, while Rust's generics are more constrained. It's not clear if Rust will ever get "specialization" for example, but Zig gets that sort of thing for free by making the whole language available at comptime.
- Zig is very explicit about memory allocation, and the standard library doesn't include types like Rust's Vec or String that implicitly talk to a global allocator. If you're writing an application that must not allocate in the render loop / control cycle / etc, that can be a feature.
- Zig doesn't have a borrow checker. If your application plans to use memory management strategies like "a bump allocator that frees everything at the end of a frame" or "a bump allocator that frees everything at the end of a query", it can be painful to express the lifetime restrictions of those allocations in Rust. Zig doesn't get in the way of those strategies, though that also means there's no such thing as "safe code" in the Rust sense.
> I much would prefer the legacy preprocessor macros C...
Holy cow you weren't kidding :)
How would you do that in safe code? If I understand correctly, the problem you're referring to is the exact reason that mmap APIs in Rust are unsafe: https://docs.rs/memmap2/latest/memmap2/struct.Mmap.html#safe...
- Is it possible for a private company to make it to space without government money?
- Is it possible to do that while competing with other companies that do get the money?
Observing that "company ___ would not have succeeded without government money" is answering the second question but maybe not the first.
- enums
- const-ness without immutable types
- no null
- no iterator invalidation
- no surprising captures in closures/lambdas
- no "spooky action at a distance"
- Cargo
For comparison "C++ shared_ptr is only safe with tree-shaped structures" would be getting-at-the-truth-but-kinda-weirdly-worded in some similar ways.
- Do the things C and C++ can do.
- Without the memory corruption issues those languages are infamous for.
- With the conveniences you'd expect of any post-internet language. (A library ecosystem that's unified around a standard build system and package manager, an async IO story, UTF-8 strings, etc.)
I would like to see a reevaluation of this take with respect to Rust. When we're talking about Rust's safety features, we usually focus on memory safety, because that has the biggest impact on security. But there are lots of memory safe languages, and I think it's actually Rust's thread safety features that are the most unique. (Lots of shared type system machinery between both sets of features.)
If you have one `short` argument to your function maybe. But if you have a `short[]` array, you probably do care about the memory layout of that array. You might need it to be compatible with some particular data format that you're trying to read/write. Same with a field of a struct, if that struct is used for parsing. A lot of C code does parsing like this.