The State of Zig and Advocating for New Languages

I don’t think it’s a language idiosyncrasy to require all variables to be used and for variables to be const if they are never mutated. It’s a heavy prioritization of reading code over wiring it. In Zig, we want to be able to have strong guarantees about the code we read. In other languages, it’s a bit more lax. I understand that can be annoying for rough prototyping, but I also think better tooling is a valid solution to this problem.

6 Likes

I could have given you the argument about how code is more often read than written myself. I’m not against having the check. My position is that unmutated variables should cause a compilation error when compiled for release. What purpose does it serve for the compiler to point out minor issues when debug is being used? No s–t there’re defects. The code is defective because it isn’t done yet. The files are still sitting in my working folder. No one is reading it. What does it matter that it’s still a little messy? I’ll come around to clean it up when I believe the code is at a state where it’s ready to be seen by others–i.e. prior to a commit. How is it unreasonable to require the programmer at this point to compile at the intended release optimization level?

This (to me) really speaks to the core of the issue. I think the fundamental belief about the release schedule is that 1.0 means that certain expectations can be practically held indefinitely.

An interesting example of this is the humble u8. I can’t imagine a future for Zig where u8 will ever mean anything other than “unsigned 8-bit integer”. As we move up the chain of abstraction, the certainty becomes less crystalline.

I’m of the opinion right now that a lot is up in the air - it’s like marking off territories on shifting sand… it’s going to change because that’s the phase it’s in. In a very real sense, none of us have ever read or written “true” zig code because Zig isn’t finished. We’re continuing to discover it and I’m sure the core team is discovering just as much.

The aliasing issue is a great example of a fundamental problem at this point. I’m very interested to see what the conclusion is about that. I’d really like to see an option at some point to enable/disable that if the issue itself can’t be resolved. The comment section on the youtube “Attack of the Killer Features” video seems to agree - that’s a really sharp distinction that divides people on the whole thing.

As someone pointed out a while ago, Jai will have similar features and Johnathan Blow’s take is (apparently) we should just recognize that it exists and don’t write code where aliasing is a problem.

1 Like

Given that this problem has existed in Zig in so long, and is considered a serious problem, if a solution existed, it would have been found by now. So I think Jonathan’s take will become the rule here too. In, fact, it has already become the de facto rule. In this case, since we have given up on the optimization being transparent to the user anyways, I would like to see a builtin @passedByValue, that tells you wether the compiler decided to pass by value or reference. At least we can mitigate the issues in code.
Either that or we find another way to optimize parameter passing, which would break the entire language. Andrew has been adamant about not going back to C’s way of always making a copy if the user decided to pass by value. So we would have to explore another more complex optimization, like what Carbon is doing.

1 Like

I disagree with this reasoning. It’s a serious problem in theory, but not in practice. So it’s a problem that can be delayed while the other puzzle pieces find their homes. There is much work to be done, and navigating the best path through this work is part of the fun of the project. Sometimes that means focusing on the language, and sometimes that means focusing on the tooling such as the build system.

In reality, nearly all of the suggestions of everyone to modify the Zig language, including myself, are fatally flawed. In order to make language changes happen that ultimately solve real world use cases satisfactorily while fitting snugly into the rest of the design of the language, it requires me to have several long meetings with only 1-2 other people whose skills include identifying quickly why an idea won’t work. Ideas are cheap. Everybody has them and according to my observation, everyone kinda has the same ideas as each other. What makes good ideas happen is when the critical feedback quickly rejects idea after idea after idea, finally making me and those others in the meeting reach for something different, something creative; something that actually solves the problems.

Regarding aliasing specifically, I still have some audacious ideas to try.

17 Likes

That is awesome to hear! The issues that refer to this problem have been quiet for some time, so I assumed people had just given up.

I don’t agree that a language should be immutable past 1.0. And looking at Rust, I think this is actually hurting them very badly right now.

There are a lot of dumb things in the language (pattern matching, named functions and closures do different things for lifetimes, green threads seem to want to make a comeback, etc.) that really need to get fixed and can’t be if you view things that way.

For example, the closures vs named functions thing bites people attempting to design GUI systems really, really, really hard in Rust. Fixing that would make a whole world of difference on a signficant class of system programming problems. Yet, the idea of fixing it is radioactive and that’s really bad.

Languages need to have evolution and, more importantly, deprecation over time. Otherwise you wind up with C++.

I see your point, but it seems industry favors stability over evolution given the success of languages like Go. Go touts itself as a “boring” language, as to say that it changes very, very rarely, ensuring practically all code since 1.0 will continue to compile and run. This doesn’t mean the language hasn’t evolved at all, they recently retrofitted generics even. But they’ve managed to evolve without breaking existing code, which at the same time makes this type of evolution very hard to accomplish. So that’s why I feel a long pre-1.0 period should help reduce the likelihood of such major changes to the language after 1.0.

5 Likes

This is a very good point. I haven’t been working with Zig for very long myself, but in the time that I have been working with it, it’s undergone some backwards incompatibilities. But I’ve looked at the plans and wants of the developers making these changes, and I can definitely see that (1) there is a significantly strong plan at play, even if I may be confused about parts of it (the LLVM stuff was one example) and (2) it’s obvious, to me at least, that the developers (such as Andrew) genuinely do care about the language and aren’t going to just abandon it. And I do think Zig is an amazing language, even if I do think that on occasion it could use some syntax sugar just to make certain idioms/patterns easier to use.

4 Likes

It’s a serious problem in theory, but not in practice

To confirm this, while the aliasing issue is on the list of TigerBeetle’s wants, its not near the top at all. In practice, we just don’t pass things using “by value” syntax, and always use explicit param: *const T and that’s good enough to stave off the bugs in a reliable way without making the code to convoluted.

Would be great to fix this, but it’s more important to fix this in just the right way eventually, than to have some fix right now.

7 Likes

Well, I understand what you’re saying. I don’t worry about it in my codebase either. But if I felt compelled to completely abandon value parameters, I would consider it as a bug that has deeply interfered with my codebase. It’s not just ergonomics, you’re giving up all value optimizations. The whole point of PRO was to make value optimizations more frequent.

2 Likes

I totally agree that the stability of a language is important. Here I just want to mention that the Go story is changing.

Go 1.21 has introduced a small change which might break some code. Since Go 1.21, panic(nil) will produce a non-nil panic. The Go team uses GODEBUG env var way to keep backward compatibility, which is not ideal but the change is so small that its impact is likely negligible.

Looking ahead, Go 1.22 will introduce a significantly larger breaking change: change the semantics of loop vars (spec: less error-prone loop variable scoping · Issue #60078 · golang/go · GitHub). The for-range part of the change is expected to make negligible impact, but the impact of the for;; part is hard to predict. While evidence suggests some code breakage (such as this one: with 1.21 vs with tip), the Go team has decided to proceed with the change anyway in Go 1.22.

So the story of “they’ve managed to evolve without breaking existing code” might end in a recent future (It will be a sad fact to me).

1 Like

At least the for-fix changes an incredibly serious footgun so the (minimal) churn seems worth it.

The footgun is obvious for for-range loops, but not for for;; loops. And the fix will create several new footgun cases (for the uses of for;; loops).

1 Like

Thanks, I wasn’t aware of the for;; case which I agree seems footgunny. Maybe that explains why Zig doesn’t have for;; (which I often miss when having to f.ex. iterate over a table in reverse).

Isn’t the while loop in Zig the equivalence of the for;; loop in Go (except that the while loop doesn’t support a InitStatement)?

It is, I like the for;; syntax more and prefer to declare and initialize the loop variable on the same line. I keep needing to invent loop variable names as the loop vars need to be declared in the outer scope (and shadowing is forbidden). This would also go really well with iterators.

In fact, I’d like to have do/while as doing something like this with an usize loop counter doesn’t work (last i -= 1 overflows):

    var i: usize = 0;
    while (i >= 0) : (i -= 1) {
        std.debug.print("var {d}\n", .{i});
    }

And switching to isize loop counters means I have to do a bunch of int casts.

2 Likes

I’d like to express my gratitude to Zig’s core team for their exceptional work. Zig is truly remarkable; it embodies everything I envisioned C could be. The more I delve into Zig, the clearer it becomes that it was crafted by someone who fundamentally understands what C should have been. It’s noteworthy how the language provides exactly what you need, not necessarily what you might want—something quite rare these days.

Regarding the question of what version 1.0 signifies to me:

In my view, the meaning of 1.0 is highly context-dependent. Specifically for Zig, when we reach version 1.0, I anticipate attaining a known “island” of stability. This doesn’t imply that it will never evolve or undergo changes, but having a robust subset of stable syntax, idioms, and standard modules will significantly enhance the user experience.

Additionally, I would value a more streamlined and regular approach to communication between the core team and the community. While I appreciate the “underground” feel of communication, and in that regard, Loris is doing an amazing job, having regular updates on the language’s state, core team objectives, and directions would be fantastic.

It doesn’t need to be an elaborate 1000-line blog post, but a monthly update from core team members, possibly in the “News” section of the ziglang website, could provide valuable insights. For instance, knowing about ongoing reworks on specific features would allow users to anticipate potential changes in their code, which, in turn, might help people who are still on the fence about building something in Zig due to the fear that things might break one day or another. Providing a quick and easy way to foresee when your code is going to break and where would be beneficial. Nobody likes to open their code after an update just to see the LSP going crazy. Having a notice that in the next update a particular feature might break lets you revisit your code to anticipate. I really like how the Rust core team communicates their work, announcing efforts to stabilize features and highlighting stable features in each Rust release. Currently, I rely on blog posts, GitHub issues, and conferences to understand the state and direction of Zig.

As for the language design itself:

I place my full trust in the core team. Despite potential disagreements, I lean towards trusting the wisdom of language maintainers. Language design benefits greatly from a unique, cohesive, and clear vision, even when considering that maintainers are human and can make mistakes. In my opinion, a unified vision is more advantageous than languages designed by committees or the community.

Furthermore, I prefer a single, potentially disliked way of doing something over numerous accommodating/context-specific approaches. Zig stands out as the only language I know that prioritizes the reading experience over the writing experience. This focus is a significant win, as emphasizing the writing experience often hinders readability. Zig’s commitment to this principle is praiseworthy, and it’s a feature conspicuously absent from many other languages.

An anecdote exemplifies this feature:

A friend unfamiliar with Zig quickly helped me solve a problem with my algorithm within 10 minutes, a task that would have taken much longer in C++. Zig’s readability proved invaluable in this scenario.

Above all:

I hope Zig maintains its core philosophy when evaluating new features or syntactic sugar. Even if it means sacrificing some aspects of the developer experience, the long-term benefits, such as ease of maintenance and extension, make it worthwhile. Zig is the first language where I don’t feel compelled to rewrite everything when revisiting my code. In less than six months of experience, I can easily navigate the std or the compiler, a confidence level I did not achieve with C despite several years of writing code.

8 Likes

I have been doing ZIG programming daily for a year and a half now, and I’ve come to a conclusion:

a) I don’t need another language to supplement my development.
b) Once you’ve tasted cross-referenced compilation, you want more of it.
c) In terms of evolution, I’ve had very few changes in my code.
d) The Language Server Protocol (LSP) is extremely useful.
e) With MACH, we have directives for the in-between versions, LTS, and the new ones in development. https://machengine.org/about/nominated-zig/#202401
f) For a very young language, it’s not bad. Yes, there are a few hiccups ( french couac), but often when trying to run nightly builds without complete documentation and wanting to anticipate… but with the open-source resources available, one can read how Zig has resolved issues in Zig’s tests on GitHub.
g) And finally, we’re fortunate to get very quick responses in the forum.

Well, it still requires some elbow grease, but that’s the case in everything for success.

9 Likes