Earlier quoted context omitted.
I think one of the main points of the article was about how shockingly few memory issues they have encountered. He talks about how great of a fit zig + valgrind is.
Indeed, but I'm curious about the second class of memory correctness issue he mentions: > 2. All other memory issues revolved around C API boundaries. Is this something Rust, or any other language, has the ability to prevent any more than any other language? Or once you introduce a C API boundary is it back to tools like Valgrind?
In addition, it's often considered permissible for C APIs to exhibit undefined behavior if their API contracts are violated. This means that a bug in Rust code that calls into a C API incorrectly can (indirectly) cause undefined behavior. For this reason, all FFI calls are marked unsafe in Rust.
The typical approach to using C libraries from Rust is to create a "safe wrapper" around the unsafe FFI calls that uses Rust's type system and lifetimes to enforce the safety invariants. Of course it's possible to mess up this wrapper and have accidental undefined behavior, but you're much less likely to do so through a safe wrapper than if you use the unsafe FFI calls directly (or use C or Zig) for a couple reasons:
- Writing the safe wrapper forces you to sit down and think about safety invariants instead of glossing over it.
- Once you're done, the compiler will check the safety invariants for you every time you use the API -- no chance of making a mistake or forgetting to read a comment.
[0]: This could be avoided/mitigated with some kind of lightweight in-process sandboxing (e.g. Intel MPK + seccomp) to prevent C libraries from accessing memory that they don't own or performing syscalls they shouldn't. There's some academic research on this (and I experimented with it myself for a masters thesis project), but it generally requires some (minimal) performance overhead and code changes at language boundaries.