Remix.run Logo
onlyrealcuzzo a day ago

Zig's incremental builds are DEFINITELY a killer feature. In the short term, I could see why you'd make a switch to get it. But, in the medium term, can we really not expect to see this in Rust in the somewhat near future?

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 [=

dabinat a day ago | parent | next [-]

This is being worked on: https://rust-lang.github.io/rust-project-goals/2026/roadmap-...

Most of the goals on this page are targeted for this year.

insanitybit a day ago | parent | prev | next [-]

Rust's compile times will get faster long before Zig gets safer.

onlyrealcuzzo a day ago | parent [-]

I'm pretty sure Zig has no plans to ever become safe - by any sane sense of the word - so, yes, I would expect...

dnautics a day ago | parent [-]

zig does have plans to give access to IRs when stable so adding a borrow checker to zig will be even easier than it is now

gpm a day ago | parent | next [-]

This is cool and will likely enable some cool tooling.

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.

slekker a day ago | parent [-]

You can because all allocations are tracked and explicit

gpm a day ago | parent | next [-]

That's not sufficient - consider the following pseudocode

    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.

dnautics a day ago | parent [-]

so yes it is possible to detect those patterns and ban them as unsafe, and have a"safety checked alternative. clr does this currently:

https://github.com/ityonemo/clr#safety-oriented-architecture

gpm 5 hours ago | parent | next [-]

It's possible to detect and ban the version of the problematic pattern that I made as short and simple as possible to illustrate the point, sure.

I'm general though, I don't believe it is practical to do so. Not without every library being designed with the checker in mind and annotated to more precisely describe their APIs. Which is why I'm not surprised to see the limitations.md that seems to exclude all the hard cases (aliasing, pointers used as first class values, cross function analysis): https://github.com/ityonemo/clr/blob/main/LIMITATIONS.md#mem...

Obviously if you rewrite the zig world to obey rust like rules and include rust like annotations you can implement a rust like borrow checker, but I don't think that it would still be meaningfully zig. It might be an interesting language worth exploring.

dnautics 3 hours ago | parent [-]

did you miss this part?

> planned to be addressed

i have in mind a strategy to address all of them. this is a side project, a proof of concept, i have other things going on in my life. i dont chip away at it every week.

you make, without any evidence ("Obviously"), an assertion that "it would look like another language". so far if anything applying zig clr would push a user to write more idiomatically ziggy code, away from idiomatically c-ish code. i dont see why continuing with clr wouldn't go further along that trend. so consider what is "obvious" to you might just be flat out wrong.

> It might be an interesting language worth exploring.

worth how much? youre welcome to sponsor my exploration and put your money where your mouth is:

https://buymeacoffee.com/vidalalabs

gpm 3 hours ago | parent [-]

I wish them the best of luck, but I don't expect them to succeed, and unspecified plans don't constitute a demonstration of feasibility. Plenty of people have made plans to solve unsolvable problems in the past, me included. I strongly suspect that that is the case here (or that they're willing to iterate away from zig).

The evidence is the amount that rust had to iterate on the underlying language to make the borrow checker work well. Something it had the freedom to do since it was co-designing the language and linter.

Edit: Didn't realize this was your project (responded before you added the donation link) - I would have worded my response slightly differently but my opinion is unchanged. Seriously mean it with the best of luck getting this to work.

dnautics 7 hours ago | parent | prev [-]

i dont understand the downvotes here. the point of any safety checker is to flag and ban potentially unsafe code, and force the author to rewrite with existing language patterns that guarantee the desired safety parameters.

in this case, zig has a first class nullable syntax that the checker can use ti guarantee correctness for, so a checker can deterministically sidestep this turing completeness issue, by squeezing indeterminate code into the knowably safer language idiom.

onlyrealcuzzo a day ago | parent | prev | next [-]

Allocations are less of a problem than aliases.

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.

dnautics 7 hours ago | parent [-]

1. rust didnt invent affine ownership. 2. It's possible to bolt on to other languages (see ada). zig in particular is easy (disclaimer: i think, i haven't implemented it yet)

onlyrealcuzzo an hour ago | parent [-]

> zig in particular is easy (disclaimer: i think, i haven't implemented it yet)

I guess it's "easy" compared to other languages - but if you think it's "easy", we have different definitions of "easy".

You could implement it, but it would look like efforts in Rust to get SPARK-like safety, and SPARK itself. It will essentially be a different language.

You will not be able to work seamlessly with any regular Zig code. That may or may not be a problem if you're willing to assume you can just use it all unsafely and it works enough that things are fine.

That's somewhat analogous to unsafe Rust. The difference with unsafe Rust is... That's a very small fraction of what you're using, not the vast majority of what you use.

When you use a Rust crate - you generally do not expect that it could have infinite race conditions. It may have some unsafe code, but that should be the exception, not the norm.

By all means, please build it. I'd consider using it [=

a day ago | parent | prev [-]
[deleted]
Peaches4Rent 10 hours ago | parent | prev [-]

Apologies for the noob question, but what is an IR?

dnautics 7 hours ago | parent [-]

intermediate representation. attempting to analyze zig code directly would be too hard (especially with comptime). on the way to the compiler backend, the compiler builds a simplified representation that only has "actually existing functions" and is very straightforward, e.g.

    function 10112:
    0: argument 0
    1: argument 1
    2: argument 2
    3: add 0, 1
    4: store 2
    5: call function 1342, (2, 4)
    6: return 5
you can see how building a data dependency graph from this would be easy.
lioeters a day ago | parent | prev | next [-]

Instead of waiting for faster compiler in Rust, how about from the other direction, adding some kind of borrow checker to Zig? That sounds more within reach and practically achievable, possibly even in userland.

onlyrealcuzzo a day ago | parent | next [-]

That's sort of what I'm doing...

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...

rienbdj 15 hours ago | parent [-]

Can selectively turn off the borrow check for dev builds?

veber-alex a day ago | parent | prev | next [-]

It's impossible to add a borrow checker to any existing language.

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.

solatic a day ago | parent | next [-]

I wouldn't say it's impossible, rather un-ergonomic. TypeScript can add type information to ordinary JavaScript code via JSDoc comments; the result can both be executed as ordinary JavaScript as-is and type-checked with TypeScript. But it's a huge pain to try to write (and maintain) everything that way, it was supported as a hack to help migrate legacy codebases. You could probably take a similar "the lifetimes are embedded in comments" approach with other languages, and the result would be similarly un-ergonomic.

AlienRobot a day ago | parent | next [-]

A better comparison would be Python.

The way Python added types is the most disgusting thing imaginable... but it has type hints now, so I guess that makes some people happy.

afdbcreid a day ago | parent | prev [-]

That is possible (clang has experimental lifetime annotations support), but that is not enough to guarantee memory safety.

As a simple example, Zig has no private fields. That makes encapsulating any unsafety impossible.

dnautics a day ago | parent | next [-]

no. You don't need private fields. All you have to do is analyze the code, harness the compiler to generate a time-dependent data dependency graph, and map allocation/frees/uses, if you can 'color' branches where data are shared you can also track and check to see there isn't an aliasing violation too.

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.

afdbcreid a day ago | parent | next [-]

This analysis is undecidable. There is a reason sound static analyzers (including languages like Rust) require in-code annotations.

dnautics 7 hours ago | parent [-]

it is possible to do in-code annotations in zig, if you're clever. you can get pretty far without them too.

as an example, you can check for double free without ownership tagging, by being agnostic about who should free, and flagging if two nondisjoint code paths attempt to free the same allocation.

AlotOfReading a day ago | parent | prev | next [-]

It seems like it'd be pretty reasonable to get something akin to polonius. I can write up an engine in zig if it'd help?

dnautics 7 hours ago | parent [-]

start by examining zig-clr

rcxdude a day ago | parent | prev [-]

It is only feasible to do this if the whole of the codebase idea designed to allow it, and it's still going to blow up in odd ways of you don't have a way to describe lifetimes in your interfaces. The magic of rust's design is that it turns this memory tracking into a local problem, such that you can design an interface and be sure that every use case is safe and verifiably so.

solatic a day ago | parent | prev | next [-]

> Zig has no private fields

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.

afdbcreid a day ago | parent [-]

I have picked private fields as an example of feature that is needed because it is very simple. You're right that you can build an analyzer (with additional code annotations) to support that, but it's only one example.

Take another example: unsafe traits. They are fundamental to some safety encapsulations, most famously concurrency (`Send`/`Sync`). Here you cannot just build an analyzer to mark something unsafe, because Zig has no traits, its generics are duck-typed.

You can, of course, add traits. But at this point you're essentially creating your own language that compiles to Zig, with all problems this entails (e.g. bad ecosystem support). It's also hard to claim that Zig can be memory safe then.

solatic 17 hours ago | parent [-]

> You can, of course, add traits. But at this point you're essentially creating your own language that compiles to Zig

I think herein lies the rub. What's the difference between a static analysis tool and an actual separate language that transpiles to the original? Hypothetically - again, very un-ergonomically - you could add traits to Zig code in comments, or in example-traits.typezig files that would be skipped by the Zig compiler (like how *.d.ts files are skipped). How much of a language is writing code in a particular syntax, versus how much of a language is writing code that will pass a tool "building" it, versus how much of a language is about the final compiled output that you get from the tool? All static analysis tools that support line-level exceptions are, essentially, programmed by comments, with their own language (typically highly simplified compared to a "full" programming language), that affect whether or not the "language" passes or not. What Typescript/JSDoc shows is that, actually, much more complicated tooling can be built with this programming-by-comments model than had been done before (to my knowledge), and thus even more powerful still tooling could be built with that model.

Of course there's a difference between static analysis and a language that transpiles. But perhaps it's more a question of degree than a simple binary classification.

veber-alex a day ago | parent | prev [-]

Exactly.

Every part of the language must support memory safety from first principles.

dnautics a day ago | parent [-]

empirically untrue. several projects exist that bolt on extra safety to unsafe languages or unsafe parts of language. SeL4 for C, MIRI for rust unsafe. i guess ada/spark for ada too, is the OG, spark being added to ada 4 years after its first release

afdbcreid a day ago | parent [-]

Hardening is definitely possible, we've had sanitizers in C/C++ for a long time. It's not full memory safety though. Miri is the same.

SeL4C is formal verification, and while it can prove memory safety (and much more) it is much more difficult, to the point that you're basically programming in a different language.

Ada/SPARK is your best example, and also the example I know the least of, so I won't comment on.

kibwen a day ago | parent [-]

SPARK omits some features of Ada, so it would only reinforce the sentiment that bolting on verifiability after-the-fact is difficult. Expressivity is generally the antithesis of static analysis, and it's very easy and tempting to make a language that is accidentally too expressive to support a given analysis without being required to make breaking changes to reduce expressivity.

dnautics a day ago | parent [-]

i mean in zig-clr it pushes you towards more expressive patterns, for example, making you label pointers as optional if their status is ambiguous

kibwen 20 hours ago | parent [-]

A language is more expressive when it allows more programs and less expressive when it allows fewer programs. I don't know zig-clr, but if it rejects programs that Zig accepts (for example, by rejecting the aforementioned ambiguous pointers), then it is less expressive, not more (keeping in mind that being less expressive is not a pejorative).

dnautics 7 hours ago | parent [-]

no thats not the definition of more expressive. more expressive means the language can encode more programmer intent without making a dog's breakfast of the code.

kibwen 6 hours ago | parent [-]

Now we're just having a semantic argument over the word "expressiveness", which is not especially interesting; see https://chrispenner.ca/posts/expressiveness-spectrum . My argument above remains true regardless of the terms you choose to use.

pjmlp a day ago | parent | prev | next [-]

Swift, Linear Haskell, Chapel, Ada/SPARK are all counter examples from such claim.

lioeters a day ago | parent [-]

Also OxCaml, from what I hear.

rienbdj 15 hours ago | parent | next [-]

OCaml already starts form a memory safe base being GCd?

lioeters 9 hours ago | parent [-]

I'm not familiar but I think this paper describes how OxCaml works:

Oxidizing OCaml with Modal Memory Management - https://dl.acm.org/doi/10.1145/3674642

> We focus on three mode axes: affinity, uniqueness and locality. Modes are fully backwards compatible with existing OCaml code and can be completely inferred. Our work makes manual memory management in OCaml safe and convenient and charts a path towards bringing the benefits of Rust to OCaml.

https://github.com/oxcaml/oxcaml

pjmlp 15 hours ago | parent | prev [-]

Yes, it is getting there, however I would rather count it when they finally manage to upstream everything to OCaml as per plan.

dnautics a day ago | parent | prev | next [-]

> It's impossible to add a borrow checker to any existing language.

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.

kfuse a day ago | parent | prev [-]

C# was already a very mature language when it had referenes and later "ref safety" added to it.

dnautics a day ago | parent | prev [-]

> how about from the other direction, adding some kind of borrow checker to Zig? That sounds more within reach and practically achievable, possibly even in userland.

It's doable, and as static analysis. see sibling comment.

Ar-Curunir a day ago | parent [-]

No, it would fundamentally change how Zig works.

dnautics a day ago | parent [-]

no, it would not. If you do not believe me, you should try out the repo.

Ar-Curunir a day ago | parent [-]

the architecture doesn't make sense. MIRI doesn't perform static analysis on MIR. It is, as the name says, an interpreter. The borrow checker is entirely different from miri.

Rust's borrow checker requires lifetime annotations. Zig code doesn't contain any such annotations. How does your design handle this?

dnautics 7 hours ago | parent [-]

1. it is possible to do code annotations in zig even though i havent implemented it in this iteration of clr (the first poc demonstrated this). i want to see how far i can get without them.

2. let's take double free (easiest to explain).

you dont have to tag ownership, you can be agnostic about who should free, and merely report if two nondisjoint code paths attempt to free the same memory.

Gigachad 20 hours ago | parent | prev | next [-]

Are compile times that big of a deal? I haven't used Rust a ton, but the few times I have it seemed like the bulk of the compile time was a one off compiling the crates, and then compiling your own code was super fast.

I feel like I'd massively prefer to end up with a binary free of memory exploits than shaving some time off compile.

Hinrik a day ago | parent | prev | next [-]

Layperson here: what is special about Go's runtime, aside from the GC?

djha-skin a day ago | parent | next [-]

Chief design goals were radically easy concurrency and speed of compilation.

minraws a day ago | parent [-]

Speed of compilation feels like a distant second in terms of goals given the weird new generic features they keep adding..

I was fine with basic generics they complicated it quite a bit much for my liking.

ameliaquining a day ago | parent [-]

What weird new generic features? Generic type aliases? Those aren't very complicated.

vips7L a day ago | parent | prev | next [-]

Is the Go GC that special? Is it even generational yet?

silisili a day ago | parent | next [-]

I'm not sure it would ever make sense to be. That makes the assumption tons of allocations get made that don't live long, which was(maybe is still?) more common in some languages. Go is more aggressive about not heap allocating, and has tools to help you avoid them.

Mawr 19 hours ago | parent | prev [-]

Idk, is it? https://go.dev/blog/greenteagc

> Is it even generational yet?

Is there any reason in particular it should be? Or are you just throwing random buzzwords around?

Anyways, https://github.com/golang/go/discussions/70257#discussioncom...

vips7L 6 hours ago | parent [-]

You seem to be really hostile for no apparent reason. There are plenty of reasons to be generational, there are lots of workloads that the current implementation might fall flat if it wasn't.

fnord77 a day ago | parent | prev | next [-]

Goroutines?

onlyrealcuzzo a day ago | parent | prev [-]

It's literally the most sophisticated scheduling engine in the world.

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.

Aurornis a day ago | parent | next [-]

> n practice, Go can typically outperform Rust in throughput (using more memory), despite having a mountain of disadvantages against it in theory

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.

never_inline 7 hours ago | parent [-]

Go has few issues with performance (lack of in-line union types, interface overuse, inefficient idioms reg. collections, some missed optimizations) but its seems plausible for a idiomatic Go program to outperform an idiomatic rust program in some situations.

Example: https://news.ycombinator.com/item?id=22336284

jcgl a day ago | parent | prev | next [-]

This is the first I've heard anyone claim higher throughput for Go than Rust. Any articles you'd point to to learn more?

insanitybit a day ago | parent [-]

I think one of the few performance benefits with a GC is that you can defer allocations. You can do that in Rust too though.

jandrewrogers a day ago | parent | prev | next [-]

> It's literally the most sophisticated scheduling engine in the world.

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.

insanitybit a day ago | parent | prev | next [-]

I think this is interesting and warrants explanation. There are cases where a GC can be faster (sort of, Arenas get you most of the gains) but "the most sophisticated scheduling engine in the world" should be easy to at least partially support.

zacmps a day ago | parent | prev | next [-]

What benchmarks are you referring to?

Rust itself doesn't have a scheduler of course, I assume this is comparing against tokio or one of the other async executors?

a day ago | parent | prev | next [-]
[deleted]
pjmlp a day ago | parent | prev [-]

What a joke, ignoring Erlang, and the custom schedulers from JVM and CLR runtimes.

dnautics a day ago | parent [-]

Erlang's scheduler is not sophisticated, which is what makes it AWESOME.

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

pjmlp 17 hours ago | parent [-]

The missing part is that if what is in box isn't enough, both JVM and CLR allow you to fully customise how the scheduling algorithm works.

dnautics a day ago | parent | prev | next [-]

> if only somehow we could get Rust's safety with all of Zig's features

i periodically throw my unused codex tokens at this:

https://github.com/ityonemo/clr

a day ago | parent | prev [-]
[deleted]