I think there’s two interesting angles to this discussion: an actually memory safe ABI and bringing stronger guarantees to Zig.
A fil-abi with broad support is kind of unprecedented. I don’t think there’s a realistic way to publish a cross language library in binary form that has memory safe properties without incurring huge IPC/VM performance/ergonomic overhead. Similar to the 0.16 IO changes, I feel like Zig is starting to take on a lot of the hard problems left by operating systems that have mostly languished since the 90s. No one really has the appetite to challenge the C-ABIs, and the zig (hostile) compatibility approach seems like a good path forward. This is orthogonal to the intra-binary safety debate–I don’t think exposing lifetimes is feasible in a binary/dynamic format. Even folks who are heavily invested in other languages should be interested in this as it could be a way to freedom from thorny C-FFIs if you want to use existing libraries or publish a dynamic library. I’m disappointed that this seems to have been mostly overlooked by the parts of the internet I’m exposed to.
I’m hesitant to bring up the intra-binary safety aspect, because formalism / static analysis is a rabbit hole and extremely nuanced. You get a lot of folks with strong opinions, who don’t really have the background to discuss things effectively. I’ve taken a few grad classes and spent >100hrs using theorem provers to formalize properties of programs and still have to invest a lot of time and check myself thoroughly. Then someone will just drop drive-by comments without digesting what you wrote or otherwise engaging in good faith (because it’s really hard). But I think it’s important enough of a subject that I’ll spend the time here. I’ll try to provide a bit of background and what I’d like to see.
The thing that most people don’t want to wrestle with is that undecidability means that there is no winning. You have to draw the line somewhere. Even seemingly trivial properties are impossible to prove about generic programs. You have to restrict what can be expressed if you want to automatically prove interesting properties of programs. End of story. This has been proven.
The trick here we don’t have to reason about programs in general. If you insert dynamic bounds checks everywhere, you’ve restricted the possible programs to those that can’t access buffers out of bounds by construction. The proof is so trivial, I’ve never seen anyone write it down. If you restrict your language to only have a single owner for each variable at a time, you can automatically prove the absence of UAF, race conditions, etc. (Fil-C is essentially the dynamic version of what affine types / borrow checkers prove). There are trade offs here. Every memory safe language that I’m aware of dynamically checks buffer boundaries because the language designers decided it would be too restrictive to force every valid program to statically reason about buffer indices.
We could likely express a huge subset of programs with PRFs and rest easy knowing that they’ll halt, not perform out of bounds accesses, and prove a lot of other properties automatically. But we don’t. We don’t even widely use functional languages.
Even if you can prove literally everything you wanted about your program, you can’t prove that what you’re trying to prove is what you wanted to prove (see the monkey’s paw or Djinn myths for how old this problem is).
Personally, I don’t really care to argue about where to draw the line in the sand. Memory safety is probably something that is worth proving for a lot of programs, but so are a lot of other things. Though, what I find compelling about Zig is being able to ergonomically express what modern computers can do. Not the absence of being able to express things I don’t want them to do.
Dynamic capability checking provides a really ergonomic way to get “memory safety”. I have a fuzzy picture of being able to use Zig’s comptime features to build refinement types/proofs to elide dynamic checks in hot paths or paths tainted by IO or ABI boundaries. You could likely just write good code that wouldn’t need extra hints to optimize.
I’m all for this proposal, but as someone who writes a lot of bare metal code, I’d just like to see the panic situation improve just a little bit. (I’ve had some ideas about abstracting over signals/interrupts that could roll up panic handlers that I’ve been meaning to explore. That may overlap with IO a bit, so I’ve kind of just been waiting to see how that plays out).
Any work on comptime proofs might open the door to more than just memory safety guarantees without the heinous baggage of ADA syntax (sorry SPARK). As I understand the theorem provers that use Curry-Howard isomorphism don’t really have a huge amount of complexity in the base language, moreso in the environment and convenience tools. So, it might be feasible to bite off small chunks or give the community the building blocks.