I believe Andrew has been talking at “Software should work” this week, and the author of Fil-C is talking also. I wonder if this is the result of conversations.
I read it as a TODO or ‘no, we are not implementing a borrow checker!’
I’ve not used Fil-C but I’m familiar with CHERI which is a hardware enforced version of similar ideas.
Basically pointers become fat and carry extra metadata. Metadata includes permission bits, bounds of a memory region and if it’s already been freed. A pointer and it’s metadata is called a “capability”. A capability can only be created from another capability with equal or higher permissions.
When a capability is passed to a piece of code it then has permission to access the resource controlled by that capability. The meta data isn’t visible to the program so it can’t be manipulated to escalate permissions. In Fil-C it’s managed by the library. In CHERI it’s managed by the hardware.
When you dereference a capability, a check is made to ensure that you’re still allowed to do that. In Fil-C this check is inserted by the compiler. In CHERI it’s done by the hardware.
End result is that you can enforce that software called at any level of the software stack can only access things it’s been given permission to access. Process, thread, library, function call… If it hasn’t been given the “capability” to access something, it can’t. As Fil-C manages the capability metadata, it can also revoke permissions so that code can’t access data after it’s freed. CHERI can’t do that, but we’re looking at extending it to do so.
It’s difficult to put the ideas into a single post, it hope that helps.
Thanks for the explanation, I admit that I have never heard of either of these technologies, so even a brief overview like this helpful. My main takeaway from the linked topic was just that the proposal was adding some new optimization mode that could produce memory-safe code using some black-magic that I have zero understanding of, so your simplified summary was very helpful.
Thank you for your answers. This is a great and simple enough explanation I think.
Re the fil-c approach vs the clr approach, I really think they are all valid parts of the proverbial elephant:
Fil-c’s “Invisicaps” seem like a really cool way to staple provenance details into every pointer. I feel like I almost understand it so I’d hope for some great teaching videos when there’s something to show. (Looking forward to watching this weekend’s presentations if I can find them!) If they pull this off while requiring no code changes, a lot of my apps would make that tradeoff. I’m hoping for this to evolve into a simple ReleaseSafeExtreme mode - I am guessing some of the 1-6x perf penalty of fil-c is maybe already present in ReleaseSafe and many of us default to that and only opt in to high perf for areas we’re willing to scrutinize more.
The clr demo of reading the Zig AST and performing more comptime analysis also sounds great, and I bet we see more of that over the next year or two as the Zig server continues to takes shape, and the original author keeps experimenting and other authors take a swing at it (maybe I’d be more excited at one using less AI assistance).
At the same time, both are beyond great proofs-of-concept but aren’t yet (as far as I understand) at the Zig level of “I’m willing to build on this as a bedrock foundation level of my ecosystem” and I’ll be thrilled to see new development based on them.
My personal approach involves dividing the world into a) whole program arena with no deinit, b) one-off global access through safe handles, c) task specific arenas, d) controlled interactions between these i.e. task affecting global state must be done by copying from c to b and having no pointers, e) enough tools, linting, & auditing to keep each of those things 100% separate and using copies only. It’s nontrivial and incomplete but it’s enough for me, and I waffle between hoping I’ll find time to write it all out to share and hoping these other tools advance so far that nobody needs it.
What I’m saying is, I’m not waiting for it, but I’m here for all of it. As long as it’s opt-in and the individual programmer can decide how to do it based on what each specific program needs.
My previous comment was meant to be a reply to this one, but I guess that got lost when editing the draft.
Based on the comment Andrew posted in the original issue thread, he is targeting for a ‘complete attack-proof’ through this ABI. I am not sure if data-race safety is also achievable with FUGC.
Since the core compiler and C codes generator of Airbus Ansyn SCADE Suite is written in Ocaml, I wouldn’t surprise if they choose Rust for the next version of the suite.
But I don’t think in this case they really care about performance either… They hand twisted the Ocaml to generate a very high optimal C codes then compile them into the box.
This is a very interesting topic. If I am not sure if in the future, they would go with pure Rust to emit the box’s machine codes or hand craft their very special C codes generator again.
I don’t feel the current box is safe… I had 3 FMGC resets last week, 1 of them in air… And I don’t currently feel the box is fast too… Pretty slow, and data racing everywhere. Multiple computers (like multi threading) race data, 010 problem everywhere… and skip navigation waypoints, failed to cache RNP FINAL APP… all happened in a week…
And I don’t feel idiomatic Rust is very convenient to write faster codes at this moment… Maybe it’s my skills issue… But at least I don’t feel I am guided into a faster codes when I am using pure and safe Rust… Again just my personal opinion.
I really hoped that Zig will become a better C alternative that actually works in a pragmatic sense. I was extremely excited about the language, the team behind it, their takes on AI and software engineering in general.
However, the more I learn about the roadmap, the less I believe in it and want to invest in the language. It feels like the goals of the project are not about making the current language production-ready, but to pursue some mystical state of perfection, flawlessness, and superiority over other languages (Rust, in particular).
What have you learned that you perceive that way? I still perceive it as relentlessly working through all the problems of C without discarding C, which I perceive as not a quick battle that we could rush to the finish line.
One example is Zig’s new IO. I always thought that Zig was about simplicity, explicitness, and maximum control. The new IO doesn’t feel like any of those. Imo, what is presented as a revolutionary way to eliminate function coloring results in a convoluted vtable-based mechanism that is quite unintuitive to consume and hard reason about.
Trying to solve memory safety is another concerning direction that probably goes against the reasons many people choose Zig over Rust in the first place.
There’s also juicy main which according to some introduces some hidden control flow.
Fil-C introduces memory safety at the cost of performance since Fil-C uses garbage collector. It is not an alternative to languages like Rust and clang/gcc C/C++ and belongs to the same category of memory safe languages as Go, Java, Kotlin, C#, etc. The same will be true of Zig’s new compilation mode, memory safety at the cost of performance.
Fair - I can totally see how IO looks like that. I perceive it as “now we have figured out the API for those that want the cross-platform vtable” plus “we are still implementing the most efficient forms of it” plus “we are simultaneously cleaning up all the std.os APIs” and I think in a bit longer you’ll have what you want but right now the lowest-level IO work really has an incentive to avoid the vtable and doesn’t know which way to turn. I think I get that, though at my level it doesn’t affect things.
I do think this Fil-C abi is a longer-term bet though - there are years-old issues and outstanding tasks about “enumerate all possible illegal & unchecked behavior” that this could help address and I don’t think this represents them dropping other priorities.
Note that Zig has a lot of moonshot issues on the issue tracker. Off the top of my head:
- Eliminate all instances of UB (not the present one, the one from 2019)
- Multibuilds (compilers knows the entire set of targets the code should work for)
- Wasm-sandboxed build.zig
- Eliminate recursion / guarantee bounded stack usage
- Closely related: transparent stackless coroutines
- Eliminate overflows by abstractly-intepreting precise integer ranges of arithmetics ops
- Native syntax for machine code, bypassing external toolchain’s assembler
Also worth consider moonshots shipped:
zigis a C++ / C compiler- Rewrite compiler from C++ to Zig
- Remove hard dependency on LLVM for codegen
- Remove hard dependency on LLVM for linking
- From-zero bootstrap without C
- Seamless C interop without C compiler in process
- Cross compilation to any OS just works
- Cross compilation while dynamically linking with an arbitrary glibc version
- Sub-milisecond incremental compilation, with in-place binary patching through linker
- No separate compilation
- Great general purpose build system
- Great general purpose package manager
On the topic of IO, note that it is exactly the same shape of the problem and solution as Allocator. Details differ, and the jury is still out whether this’ll be the last implementation of async in Zig, but any general argument that applies to IO applies also to Allocator, and therefore proves too much, as the allocator API is a wild success.
I appreciate you listing those out, I think people are quick to rush towards 1.0. All of these are incredible achievements. On the topic of IO I think that there is something to be said that the idea of the way that you work with it feels really solid, IMO, it just needs a good couple of iterations.
People are talking a look at languages like Odin reaching 1.0 by early 2027 and Mojo reaching 1.0 later this year, and Rust reaching 1.0 within 6 years of its initial commit, and wondering why Zig is still nowhere near 1.0.
note that it is exactly the same shape of the problem and solution as Allocator
I don’t think that’s true at all.
Concurrency is optional, memory allocation is not. The code performing sequential IO operations can totally avoid paying for vtable lookups, but one cannot avoid memory allocation in some way (unless it’s the stack).
Also, there is no way to allocate memory in two ways at the same time, but combining different concurrency mechanisms is a totally viable thing in many programming languages, e.g. running multiple event loops in separate threads.
This is incorrect. Rust 1.0 shipped in 2015, the first presentation about Rust dates from 2010 (https://venge.net/graydon/talks/intro-talk-2.pdf), and at that point the language was already functional, having been in development “for the past four and a half years”. The first commit was in 2006: initial commit · graydon/rust-prehistory@b0fd440 · GitHub