> 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.
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).
By the way, I thought your question was totally reasonable - my first thought reading it was "Oh yeah I wasn't trying to say that writing bytes is unsafe, I definitely should have worded that differently."
> although of course that wouldn't work for running tests.
Why not? Unless you mean in the cross-compilation case, in which yeah, to run the compiled tests you'd need an emulator.
> in the specific case of Rust I believe rustc only compiles the tests and then something else like Cargo executes them.
It doesn't have to be Cargo, but yes, rustc produces executables for the tests, and you have to then run them.
> there's the same opportunity for end user memory being corrupted (due to miscompilation)
I agree for sure that the safety of the outputted binary is completely distinct from the safety of the compiler itself.
I think the reason that this framing specifically (in the post and in this comment) strikes me as odd is that "requires unsafe code" sort of implies that you need to use unsafe to fix the unsafety of the outputted binary. That just isn't the case. Of course, this is a serious bug that needs to be fixed, but there's just something about "doing memory unsafe things" in this area that like, I think can be a little mis-leading, even if that's not intentional. But I am going to sit with this and think about it, regardless, because I am not sure that my gut reaction here is completely accurate.
(And, hilariously, looking over some work my agents did on my compiler last night, they fixed some mis-compilations that occurred, entirely in safe code. I bet that's also part of why I'm in this headspace at the moment, it's not like those fixes required dropping down into unsafe to fix either!)
Your tests run in an entirely separate process from the compiler (and from cargo). This makes it very different from memory corruption in the compiler:
- The test process can only corrupt its own memory.
- You don't need "unsafe" to run tests. Just the ability to start another process.
- If you're cross-compiling, you wouldn't even be able to run the tests on the same machine (without emulation/compatibility layers)
Does roc run tests in the same process as the compiler?
We do for tests of pure functions, yes.
> Your tests run in an entirely separate process from the compiler (and from cargo).
That's a great point and a relevant distinction, although Rust tests can run arbitrary I/O, so it's not like having them be in a separate process means memory corruption is harmless! :)
> rustc emits machine code and then cargo immediately executes it, there's the same opportunity for end user memory being corrupted (due to miscompilation) as if rustc and cargo shared a code base.
Cause this hasn't been true for me or for anyone maybe your definition of memory being corrupted is the not same as mine.
I am not even sure what you are trying to prove with this.
I appreciate the time and effort in building stuff like Roc I don't use it but this comment and the article feel like...
Oh some guy said Zig not nice because memory safety so here, a post why memory safety doesn't exist because we have to do memory unsafe things sometimes and so everything is memory unsafe already, so maybe it doesn't matter.
I get the energy that we are going for seeing useless claims and wanting to push back but I think the article deserves a clearer part 2 where you elaborate on your thoughts about stuff maybe even get it peer reviewed a bit before posting or maybe don't I guess we could use more raw thoughts in the post AI age.
Either way I appreciate someone trying to put forward their own thoughts and explain problems with a different perspective.
Well, I've personally have written a const-expression evaluator that actually reuses the rest of the compiler: it compiles the expression in the current environment with some specific adjustments to the codegen settings, launches the temporary executable and gathers its output... frankly, it's more hassle than it's worth compared to writing a separate const-expression interpreter. Plus, of course, it also runs slower since most constant expressions are usually pretty trivial.
It is like someone arguing that since they always bump the head somehow while wearing seatbelts, then they are only a nuisance and should not be used.
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.
It's worth noting that the reason Rust doesn't include support for custom memory allocation patterns like Zig does has nothing to do with memory safety. It's more of a historical accident that it just wasn't something that was prioritised early in the projects history and is now hard to change.
It will be nice when the Allocator trait stabilizes so that the ecosystem can coordinate on making this stuff pluggable, but that's not a direct blocker for getting things done if you need to do things today.
On that topic, worth mentioning that Rust's long-awaited `Allocator` trait is perilously close to stabilizing; watch for https://github.com/rust-lang/rust/pull/157428 to be merged, then the stabilization PR to progress here: https://github.com/rust-lang/rust/pull/156882
A lot of the ways in which the zig compiler works doesn't use pointers, it uses indices. This stuff is easier to write as safe code, not less easy.
> 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.
I do think that that makes sense, but it also doesn't mean that they have to. I am doing a compiler project that takes a lot of inspiration from Zig (as my language currently inherits some major things from Zig, and I also care a lot about compiler performance) and it's written in Rust, and does not use much unsafe code (outside of the usual suspects of FFI in the runtime, etc).
I respectfully disagree. This is only true if you view malloc as qualitatively different from a piece of code that gives you an index for a free object in an object pool.
Provided you don't ask/give back memory from/to the OS, what malloc is doing is giving you an index (pointer) into a pool of bytes, while manipulating an internal bookkeeping structure.
Use after free is just you using an index after said bookeeping structure has marked that piece of memory as available for something else (and perhaps claimed already).
If you have an array of Node structs to represent a graph (like the AST in Zig), and use indices to represent references, you have essentially zero protection in Rust that helps you with finding Node-s that have been used for something else etc.
The 'asking memory from the OS' aspect for malloc doesn't really change the safety of your language compared to this where it matters - if you do use-after-free on a page claimed by the OS, you get a segfault, which immediately tells you there's a problem, which is much better than silent corruption.
At least with malloc, you get debug allocators, or other features that can help you in this case. If you are careless with indices in an object pool, and overwrite stuff, essentially, it's up to you to figure out what went wrong and you have no tools to help you.
> you have essentially zero protection in Rust that helps you with finding Node-s that have been used for something else etc.
The most obvious technique is generations. You can of course do that in Zig as well.
> if you do use-after-free on a page claimed by the OS,
This assumes that you're working in the context where there is an OS. That isn't always the case. Also, there are other cases than just use-after-free: for example, compilers will optimize around null pointers being UB, which can cause other problems, whereas an index of zero does not get the same treatment.
But also, again: Zig does not use malloc for its ASTs, as far as I know. It uses lists and indices. I haven't literally read the code lately myself, but I would be surprised if they went back to malloc'ing individual nodes.
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
Anywhere talking about the `unsafe` keyword is within the Rust code.
> Regardless of which process had the bug—the compiler or compiled program—in both cases the processor only did the bad thing because the compiler told it to. And in both cases the fix is the same: the compiler's code must change, since that code was what caused the memory corruption.
But yeah, I wonder what those 1,200 unsafe uses actually did?
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.
It's not about the memory safety of the resulting binary.
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)
It is only relatively recently that we have gained more realistic options in these spaces, and so not fully understanding the implications, or preferring the historically normal choices, is understandable.
Safety [against something] is also a product of components - a system made up of only safe components [against a thing] is safe [against the same thing... I'm going to stop this qualification now for brevity]. A system containing unsafe components may or may not be safe but at least you know what components usage you need to look at carefully.
If your linker is safe, linking code will never result in the thing it is safe against. Ever. This is a useful property even if running the linked thing is not safe because it means:
1. When things go wrong in strange ways, you have strict bounds guiding you in figuring out what went wrong.
2. You can build reliable systems that do part of the job, and only have to sandbox the other half of the job. Compiling in a CI system will (if the compiler was entirely safe) be safe. You can do it with secrets present against malicious code. Running tests will have to be sandboxed (assuming running tests isn't safe). This could for instance enable safely sharing significantly more artifacts for incremental builds in CI.
Unfortunately very few compilers are really safe against anything (though I do wonder how I could break my toe on one). Rustc for instance has a giant C++ half called llvm that isn't really hardened at all. We get away with this by just not trusting the compiler when run against potentially malicious code.
1. Foundational for other forms of safety
2. Has an objective definition, when some other forms of safety are either subjective or inter-subjective.
That said, I don't understand why your parent brought this up to you, you are talking about memory safety in your original comment here, so that's what Rust's safety is about.
It's more that Rust's safety guarantee is memory safety. No more, no less. It's not about buzz, this term was used long before Rust existed.
> it also has the gaping type system holes demonstrated in cve-rs
This is not a "gaping hole". It is a compiler bug, which has never been found in the wild.
> there are other bugs which occur in Rust
This is true! Every language can have bugs in it, and Rust does not claim to solve all bugs.
Yes.
> If so, why hasn't it been fixed yet?
Pretty classic software engineering reasons.
The part of the system that it involves was in the process of being re-written already. The re-write fixes the bug. Because it is essentially a theoretical issue, and not an actual problem in any real code, it is not a five alarm fire. Waiting for that re-write to land makes the most sense, instead of putting in a ton of work that will be thrown away.
Other, more serious miscompilations get fixed faster. In fact, a version of the Rust compiler was released today to fix one, even https://blog.rust-lang.org/2026/07/16/Rust-1.97.1/
This one was impacting actual users, and did not require re-writing entire subsystems to fix properly. So the engineering and product tradeoffs are different.
This is impossible. General words like "safe" and "good" are subjective, and useless in a technical context unless you ground the discussion by giving them specific definitions. Otherwise everyone ends up talking past each other.
Safe for what? My house is safe for humans, but not safe for tropical birds.
Clean enough for what? Our water is clean enough to wash my ass, but not clean enough to wash a telescope mirror.
Sorry but life is not a Disney movie where some things are unequivocally good/safe and other things are unequivocally bad/unsafe. There are gradients and conditions, and communication requires a shared language between participating parties to navigate them.
Because a “very specific form of safety” is a useful tool in achieving “safety in general”
Because a “very specific form of safety” is tractable for a compiler and language runtime to achieve, “safety in general” isn’t
This is a core perspective disagreement. While this is true:
> If your system gets hacked by a buffer overflow in the end, nobody cares whether it was the linker that overflowed or the code emitted by the linker.
That does not mean that increasing the amount of safety in the individual components isn't helpful, because it helps minimize the above outcome, even if it will never be zero.
If anything, compilers are perfect models of trees and well formed programs.
That said I'm struggling to think of something that would need to be unsafe.
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 am talking from experience from a pre-ai human mitts writing code perspective maybe Zig + LLMs do some magic.
The more I read the article the more I feel like this is just bad not sure if I should be giving it as much latitude as I have been in my prior comments.
There are other claims as well that are weirdly phrased at least.
Reads like an article written to justify some arguments they had rather than a genuine take at this point.
But I will give the benefit of doubt I enjoy weird articles, languages and share a dislike for aggressive AI-ness of all things.
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' :)
To clarify, is that to say that you have to use the `std.heap.page_allocator` as its backing allocator?
The bug was around passing a slice to OpenGL which referenced memory outside of its lifetime. Since the memory location had no owner, vertices would still exist in Dev builds and everything would work fine, but in ReleaseSafe the application would run and just have nothing to render.
Since OpenGL was trying to read the memory, there was no panic from Zig, but it was a cool look into how the different build modes handle memory.
This is the commit where I fixed the issue: https://github.com/quot/donut/commit/8fff107e76278c4bf55007c...
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?
> 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?
From what I remember, it was a whole-sale re-write from scratch, not a transliteration. While Rust took a lot of inspiration from OCaml, especially in those days, it was different enough that I'm not sure that a more direct transliteration would have been particularly possible, though again, see above, I wasn't there, so I don't know for sure.
Unix system programming in OCaml
Reading the average HN opinion, it seems everybody is writing high-performance latency-sensitive systems that would implode if a response would take 1 ms longer than normal.
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 [=
I don't think a borrow checker is likely to be in that tooling. Borrow checking requires shaping the code, and all the dependencies, into easily analyzable (and at least in rust's version annotated) patterns. You can't borrow check arbitrary code not designed for it without false positives.
x = malloc();
if (opaque_cond()) free(x);
if (other_opaque_cond()) use(x);
Conditions can be opaque and non-analyzable due to rices theorem - in any turing complete language. This code is correct (or at least not memory unsound) if opaque_cond and other_opaque_cond are never both true. Otherwise it isn't.And functionally compiler analyses of whether conditions hold have to be trivial because using some form of theorem prover to decide of code is correct or not leads to code that is brittle against compiler version changes, and slow compile times. Thus opaque_cond could be as simple as `len == 0` and `other_opaque_cond` could be `len > 0` and it's unlikely you'd want the compiler to realize those are mutually exclusive (at the stage where it accepts programs, obviously during optimization it is very likely to take advantage of this).
Rust solves this by simply rejecting the pattern. Very roughly forcing you to write if opaque_cond() { free(x) } else if other_opaque_cond() { use_x } (or something else where the program structure and not just the logic in the conditions guarantees correctness). Zig simply allows it and leaves it up to the programmer not to make a mistake.
And as onlyrealcuzzo suggests aliases are where this type of analysis (accepting enough programs to be useful but still imposing enough structure you can prove correctness) is really tricky.
Without affine/linear ownership - solving the aliasing problem is the Halting Problem.
Rust didn't invent Affine Ownership just to make Rust hard. It did it because it's one of the only ways to have memory safety without a GC.
Most of the goals on this page are targeted for this year.
I was fine with basic generics they complicated it quite a bit much for my liking.
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.
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.
Rust itself doesn't have a scheduler of course, I assume this is comparing against tokio or one of the other async executors?
but yeah. i would be surprised if the JVM's scheduler is not more sophisticated than go's if for no other reason than it has way more knobs you can tune. you know they put that knob in there because someone (probably Google cough cough) asked for 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...
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.
As a simple example, Zig has no private fields. That makes encapsulating any unsafety impossible.
it is easy to patch the zig compiler to enable this this (export the code graph; about 50 LOC). The analysis is much much harder to get right.
You may have missed the point here. You could add a comment to the struct field that marks the field as private, and build a TypeScript/JSDoc analogue that analyzes all accesses to the field and fails if it finds accesses from functions that aren't part of the struct that owns the field. You don't even need a comment on the field - you could copy Go's convention, add a comment to the struct definition marking it as "follows Go convention", and then fail any access from outside the struct to a field that starts with a lower-case character.
It doesn't prevent you from ignoring that tool and writing Zig code that imports the struct and accesses the field. It is, of course, not part of the Zig language itself. But if you adopted a tool like that, it would be your responsibility to run it across-the-board and pay attention to the results - same as how it is your responsibility to pay attention to the results if you added those JSDoc comments.
Every part of the language must support memory safety from first principles.
Why do you say that. Have you tried and failed? It seems to be possible to add a borrow checker to zig, just as you can add MIRI to rust to get extra safety in unsafe blocks.
It's doable, and as static analysis. see sibling comment.
Rust's borrow checker requires lifetime annotations. Zig code doesn't contain any such annotations. How does your design handle this?
i periodically throw my unused codex tokens at this:
I know from experience that this initial assumption is wrong. Compiler performance is dominated by algorithms. The fastes managed languages tend to be at worst within a factor of two for wall time on any given algorithm. Algorithmic differences can be unbounded in their performance gaps. Zig itself is a perfect counterexample to the theory that writing a compiler in a low level systems language will lead to a fast compiler. Roc seems to compile at around 15k lines per second. That is not fast. There were evidently compilers written in ml that did 3k likes per second in 1998 https://flint.cs.yale.edu/cs421/case-for-ml.html
The zig rewrite of roc looks like the author's second compiler. Compiler and language design is a skill like any other and from my vantage point, they appear to have overcommitted to an initial design at the expense of developing their higher level design skills. In my opinion, the best thing they could do for the future of roc is stop working on their current compiler and use it to write a self hosting compiler for a much smaller subset of roc. They should be able to do that in less than 10k lines of code. They might even find that their self hosting compiler is faster than their zig based bootstrap compiler for the self hosted subset of roc. If the self hosting compiler is inadequate. Now they at least have identified a smaller useful subset of roc and can experiment with different compiler implementations in 10k likes of code rather than 300k lines of code. Then they could actually test the theory of whether or not a low level language is necessary to meet whatever arbitrary compiler performance goals they have.
By self hosting, they would also discover what roc features actually matter and they would spend much more time actually writing roc code. The features that are needed to write a self hosted compiler are all features that are generally useful. By improving the self hosted compiler, they also improve downstream programs.
Being able to compile ML quickly in the 90s tells you little about being able to compile Roc or some other language today because the language design enforces hard constraints on the algorithms necessary to compile it and the hardware today is much more complex. It's not hard to write a fast Pascal compiler that targets a 1980s chip with shallow pipelines. But that's not the problem being solved here.
I don't know much about Roc but it looks like it's got some amount of overloading and the linked article alludes to sophisticated algorithms to avoid heap allocating closures. Those can enforce algorithmic complexity in the compiler that is essential and can't be eliminated.
Once you're at the limits of algorithmic optimization, all that's left is reducing constant factors. I've written code in many languages in different performance regimes over the years and it's certainly the case that higher level languages, especially managed memory ones, put a hard floor in terms of how low you can go when optimizing to improve those constant factors.
I have seen in real-world code where explicit control over memory layout improved performance by more than an order of magnitude. I have friends in the game industry where much of their career is this kind of work. Those people would love to live in the luxurious world you describe where all they need to do is find a sufficiently clever algorithm and all of their performance problems will disappear.
It's supposed to be a scripting language right you embed into your C ABI right?
Do you see it competing with WASM for the plugin use case (i.e. a really large Roc platform)? Why would an app author prefer to expose a Roc layer to their app rather than a WASM layer? With a WASM layer, plugin devs can write in any language.
Another use case I've heard from it is as a more app-level language (i.e. a really small Roc platform). Do you see it competing with Gleam for server side http code? Do you see it competing with Elm for client side code?
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.
The compiler is one of the most significant trust boundaries we have. Its decisions can intentionally or unintentionally create vulnerabilities in programs compiled by the compiler, which means that if you can compromise a compiler you can compromise everything downstream.
Unsafe memory access in a compiler can be exploited in order to hijack the compiler itself (this is reported regularly in production compilers), allowing the attacker to then insert arbitrary code into compiled binaries. Not everything that a compiler absorbs from its environment is meant to be treated as source to be compiled, and in a memory unsafe compiler any of that input can silently turn into machine code in the compiled binary if an attacker is able to exploit the memory safety bug and hijack the compiler.
1. There are lots of things that compilers load into their memory that aren't actually source code. A memory exploit turns non-source data into executing-in-the-compiler code.
2. Depending on the language semantics, a memory exploit can allow substantially higher privilege than just being loaded as library code. Latent malicious code that never gets called into never becomes active, but if you can exploit a weakness in the compiler you can make your code execute at any time you'd like instead of relying on the main application calling in to your malicious library.
And as mentioned, if what Zig offers is already in Purify, there is hardly any added value over C and C++, without the headaches of a niche language.
On the other hand, it would be good to garbage collect those caches. We are wrapping up work on a new layout for intermediate build artifacts that will make it easier to GC them.
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)
Having nearly one million files in nodes_modules isn't that unusual. The problem is that on most common file systems the minimum allocation is usually at least 4KB. So even if the actual data is less than 500MB, you end up with 4GB disk space used/wasted.
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.
This is a solved problem in other projects. Either use the version numbers as intended and bump the major version number on breaking changes, or use Rust-style editions to opt in to the newer versions of the changes.
Calling a project production-ready but keeping the version number below 1.0 and saying breaking changes are expected is a tired game. We've seen it backfire across a number of language projects like Elm, where the exact same claim was used to both encourage people to use it and then blame them when it backfired.
If it's production ready, go to 1.0 and then follow semver for breaking changes. I don't care if we get to Zig v73.2.0 as a result. At least we can see from a glance which versions need to be checked for breaking changes.
on the other hand, a language with frequent breaking changes should not be considered production ready.
people are of course free to live on the edge, and if someone decided that zig is good enough and they are not bothered by breaking changes then they are free to use it for their production system, but that doesn't mean it's ready for everyone. so i prefer the zig approach.
Except that means that not only you lose compiler bugfixes, you also pretty much has no access to the ecosystem. For most production codebases, this is a deal breaker.
That sounds like it's not ready for production to me.
To me it is not much different from Lua, which despite being on 5.x for decades, makes breaking changes on minor releases (because it predates SemVer).
I also don’t see it being much different from any other language or language runtime that has a major release every year.
It’s fine to update at your own pace.
I did, and I immediately found this in the latest release: https://ziglang.org/download/0.16.0/release-notes.html#IO-as...
That seems like it would require changing a lot of code. Calling it "production ready" is dishonest at best
I am not sure, but there might be a bug in their pattern matching example.
What happens if 'verb' is "GET" and 'path' is "/users/1234/posts/1234/extra_path/and/more/"? Will 'post_id' become "extra_path/and/more/"?
I tried running it in the sandbox, and it does indeed seem to buggily result in:
"Post ID: 1234/extra_path/and/more"
I suspect that the reason it is behaving like it is, is due to how it handles characters in the string literal. The example program exploits that only the slashes present in the string literal pattern are matched, to enable matching on 'page' having slashes. But then in the nested 'match', it forgot to account for any possible extra slashes.
Nitpicking end.
I have not read the whole post yet, but the pattern matching not requiring any allocations, seems very nice. The string literal patterns also seem interesting, though I am not completely sold on them, also as per the above possible bug. It seems really clean in some ways, but the specific semantics, I am not fully sure about. Maybe it is excellent, and is so clean and concise that it is overall less bug-prone than alternatives in other programming languages. I do not know.
Of course, reasonable people may also believe that it is easier to use an unsafe language directly rather than change the ways that you code.
In my experience doing embedded, operating systems work, compiler work, and others, you never need a large amount of unsafe code. 1%/4% is really about it.
As they state in the article, they started the migration a year and a half ago, something that happened a few weeks back would never come into the decision making process.
I think precious cognitive time should be spent more on the language itself rather than wasting it on rewrites.
The simplest solution to these problems, if you have the capital, is to buy them.
Rust is also one of the best languages to use with AI.
That being said, I had to do some double takes while reading this.
> https://rtfeldman.com/rust-to-zig#memory-safety-post-rewrite
I feel that it's a bit weird to compare a rather well tested 7 (?) year old rust implementation with a brand new not yet released less than a year old Zig implementation. Without that context, this looks like a bad comparison for rust, when it is in fact the complete opposite.
> https://rtfeldman.com/rust-to-zig#build-times
The swiftness of the Zig compilere here is insane, and would would very much shift my recommendation of Rust if it got to similar speeds.
That being said, I do find it funny that currently, the compilation speed is actually worse on Zig than Rust, despite Zig (anonymous commenters at least tbf) claiming the opposite for years.
How did you eventually discover the 35 ms figure for Roc? Did you have to temporarily update the codebase to 0.17?
> https://rtfeldman.com/rust-to-zig#memory-control-zero-parse-...
Nothing negative here. I did play around with implementing a scripting language in this DOD-ish, index-based paradigm and yeah, it is neat.
I was thinking that it might be possible to do resumable computation across the network like this (in the context of frontend frameworks "resuming" UIs), but ultimately I have no use for this so just the experience itself was enough.
One note here is that it does tend to break completely if non-pointer-free data is introduced. It seems like it's either all or nothing.
> https://rtfeldman.com/rust-to-zig#ecosystem-relevance
This is more of an LLVM thing, which is fair, but I find it funny that "LLVM unstable bad" while "Zig unstable whatever".
Overall though, this was an interesting read. And if the folks contributing to roc like zig then more power to them.
Last thing, the link here is broken (points to a TODO):
> Zig's compiler itself is another
wondering what type of project is that? I think besides some very embedded projects with very little memory where you need C/assembly, rust is good enough for all kind of projects..
The runtime performance is much better, but the compiler time performance is terrible. To be fair, this is mostly the fault of async-graphql, but that doesn't really matter all that much. For example, it's not uncommon for a single character SQL query change to trigger over a minute long incremental rebuild.
The rust compiler is just choking on the number of generics and codegenned functions.
I've personally looked at how to improve this, but short of breaking up the type graph using federation, nothing can help. Not even cranelift makes a noticeable dent.
Additionally, the team started off composed by a bunch of TypeScript/React/Node developers, so mistakes were made along the way.
Honestly, I would have recommended to just use C#.
That's not to say that I don't think Rust can work for web development. We have some (GraphQL-less) services where Rust is a great fit. Just maybe shouldn't have been the default. That or give up graphql ...
I have the same issue with "use the right tool" rhetoric. The right tool is the one that does the job and that you know best.
Some folks embrace it as some kind of novelty.