FR version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
81% Positive
Analyzed from 2042 words in the discussion.
Trending Topics
#rust#code#memory#unsafe#zig#language#compiler#machine#runtime#more

Discussion (50 Comments)Read Original on HackerNews
> remember that for compilers which emit machine code, like roc and rustc, doing memory-unsafe things is a big part of the job
I don't really think that this is true, in the way that it's written.
I think that for the hot binary patching / code reloading features, yes, that is going to need unsafe. But for regular old "producing an executable" compilation? Emitting machine code isn't the part that requires unsafe. The language's runtime is a more likely site to find unsafe.
I don't think that's any different either. The core job of linking isn't particularly unsafe.
(Unless, similarly, you're doing the hot reloading stuff)
In extremely high performance code you use different data structures and algorithms and change your approach to memory allocation. TigerBeetle famously does all memory allocation once on startup.
Roc is attempting to make a similar set of trade-offs in their compiler as Zig, so it makes sense that the author finds many shared patterns.
The compiler itself might be perfectly "memory safe" but the generated binary fundamentally is always at risk (besides WebAssembly I suppose).
I'm fully aware of the separation of compiler and binary, and being able to compile untrusted code safely is nice, but a perfectly safe compiler that generates vulnerable binaries isn't that much better.
> Zig has more features than Rust for making memory-unsafe code work correctly, and that was the area where we wanted the most help.
Zig definitely does not have more features for successfully emitting memory-unsafe machine code than Rust does. I can emit memory-unsafe machine code from typescript if I really want to and nothing at all in the language will get in my way. So the sentence quoted above must refer to the idea that the compiler itself needs to be unsafe, which Steve is right is simply untrue.
I am also probably in a more pedantic mindset because, well, I'm writing a compiler in Rust, and the words as written do not resonate with me at all.
> a perfectly safe compiler that generates vulnerable binaries isn't that much better.
I do think it's much better. Eliminating classes of bugs in one component is a good thing, even if it's not every component. This is a core lesson of Rust! unsafe still exists, but going from "I don't know what is unsafe" to "only this part is unsafe" is a major improvement.
Agreed! Emitting machine code is not unsafe, since it's just writing bytes down - it's only once you execute that machine code that there's potentially unsafety. The reason I said "a big part of the job" is that in practice a lot of compilers both emit machine code and execute it - but you're totally right that it's not a requirement that a compiler do both.
In addition to the examples you gave (hot binary patching/code reloading, language runtime, etc.), others would be things like evaluating userspace code at compile time (e.g. const fn in Rust, or in Roc any expression that could be hoisted to the top level), running tests and inspecting their output to decide what to display to the user, etc.
Those are the types of things I had in mind when I wrote that.
I also think it's a good thing that you wrote the post in general, when I saw it pop up I was like "oh, of course, this post should exist!" I'm surprised I didn't think about it earlier.
> evaluating userspace code at compile time
Usually this would be done via an interpreter, so I'm not sure that it really requires unsafe either. If you are literally executing machine code, sure, but const fn in Rust and constexpr in C++ and many other languages do not do that, as it causes a number of problems (for example, cross-compilation).
In this respect, effectively all the compiler should be treated sort of like an unsafe region because it requires extra care to avoid memory corruption bugs.
> we ended up with about 1,200 uses of unsafe
> remember that for compilers which emit machine code, like roc and rustc, doing memory-unsafe things is a big part of the job
If anything, compilers are perfect models of trees and well formed programs.
Cross compilation is great, but not mentioned in the "why Zig" section. Is memory control that crucial for a compiler?
Rust itself was originally written in OCaml, same with WASM. I'm curious about what milestone gets reached where the maintainers collectively decide to transition away.
Since you're here, could you comment on the approach Rust took in their rewrite? Was it more of a straight translation like Go did when they self hosted -- similar to the recent Bun transliteration? Or were there architectural changes made along the way like this article describes with Roc?
I don't know Zig so maybe they know something I don't, but I have seen no evidence that it catches any type of use-after-free including double-free?
While writing a blog post (below) I went through the documentation to figure out the possible runtime memory safety checks Zig can insert. The term "use-after-free" or "UaF" never occurs on that documentation page. Searching for "safety-checked" doesn't yield any related hits either.
Unless maybe they're using the DebugAllocator in release builds? Even that does not reliably surface UaF.
https://landaire.net/memory-safety-by-default-is-non-negotia...
I think ReleaseSafe just adds bound checking and panics on unreachable code.
I don't think Zig offers any temporal memory safety.
https://ziglang.org/documentation/master/std/#src/std/heap/d...
For higher level code, "generation-counted index handles" might be the better solution to provide temporal runtime memory safety, not part of Zig the stdlib though.
Or even better: never use dynamic memory allocation and make all lifetimes 'static' :)
If I want to use allocator debuggers I already have the production ready tools that exist for C and C++ for at least 30 years.
I want to go fast, but I don't want to go fast just to shoot my foot off.
If only somehow we could get Rust's safety with all of Zig's features and Go's runtime without GC...
That's what I'm working on building [=
Most of the goals on this page are targeted for this year.
In practice, Go can typically outperform Rust in throughput (using more memory), despite having a mountain of disadvantages against it in theory.
That's how good the Go scheduler/runtime is.
This is a huge claim that disagrees with both my real-world experience and everything I've seen from artificial comparisons.
Every high performance Go system I've worked on has quickly reached the point where we're optimizing memory management and doing things that would have been explicit in a non-GC language like Rust anyway.
The Go runtime is amazingly optimized, but it comes with overhead over doing the same work directly in a lower level language.
Rust itself doesn't have a scheduler of course, I assume this is comparing against tokio or one of the other async executors?
That seems unlikely regardless of how good it is. This is a domain where state-of-the-art research is not in the public literature. Scheduling is an AI-complete problem.
i periodically throw my unused codex tokens at this:
https://github.com/ityonemo/clr
The reason Rust has a working borrow checker is because every part of the language from structs, enum, traits, generics and all the way to the syntax itself has been designed to support lifetimes and borrow checking.
It's is not something you can just tack on to an existing language without fundamentally changing it.
I'm writing a language with Affine Ownership that transpiles to Zig and has a built-in FSM-based Green Fiber runtime.
Affine Ownership gives you memory safety + fearless concurrency + eliminates the need for Go's GC.
It's obviously going to slow down compilation - since you need to do Rust's borrow checking, etc. But I can do this incrementally as well...
It's doable, and as static analysis. see sibling comment.
My Tauri project, where the backend is much smaller code-wise than the frontend, has 9gb of rust artifacts (node_modules is 550mb for comparison)
Nowadays when you can just point an agent at release notes and have it update everything, I actually prefer not having to wait through rare major releases to get new language features.