Post-mortem

Hi all. Experienced dev here, I thought I’d do a quick post-mortem after shipping my first real Zig project.

  • I am deeply inspired by Ghostty/Mitchell and decide to try Zig for my next project. I use 0.15.2 on macOS with an assist from mise.

  • OMG compilation is fast! Love love love it. After doing a similar project in Rust I can’t go back. The binaries are tiny as well, I am hooked.

  • LLMs are good at zig. IMO the docs need a colossal amount of work and this is made worse by the strata of ancient pages, blog posts, and dead code on github. It’s hard to find definitive docs. Docs site seems slow. Personally I find this odd since docs are easy to improve with an LLM. I accept this though, no big deal.

  • It’s wonderful to see that LLMs will happily scan std and make good decisions. I pair with Codex and use Claude for reviews, which works pretty well.

  • Best bug so far - Codex thinks all my tests are green, but it turns out there are no imports (and no output). Claude catches it.

  • I can’t find lots of essential things that I looked for in std. No csv. No regex. Unicode display width. argv. Some libs are on github but look abandoned or are self-described toys. Again, not a showstopper. I know how to build this stuff and it’s not out of reach. Looking forward to juicy main.

  • Enjoying the language design, for the most part. No surprises, feels familiar, like slipping into new shoes that are the right size. I like structs, unwrapping optionals seems straightforward. try/defer are natural and fun after writing lots of golang.

  • I am quickly learning that the hard stuff is easy - compilation speed, binary size, fast iteration, code hygiene with fmt and friends. Likewise the easy stuff is hard - strings, printing, filter/map/sort/sum. Formatting numbers with delims.

  • I wrestle endlessly with certain tasks, like accepting input from either stdin or a file. Or how to surface error strings from ARGV processing. The lack of message: for errors is painful. I largely give up on memoization, which is a powerful pattern.

  • Now that I’ve written a bit more Zig I feel some minor frustations with the language. Variable naming collisions are maddening and occupy too much brainspace. Still don’t quite understand all the nuances of const vs. var, despite my lengthy experience with similar constructs across many languages. Eventually I tuned out and let the LLM handle it. I miss default arguments and named params. Why can’t I globally import std? Not crazy about break :blk or the errdefer pattern, but I get it. Where are my precious lambdas? Sometimes I feel like I’m typing too much in Zed.

  • LLMs work with great confidence when tests are co-located with source. My regex implementation has got to be one of the most heavily tested things I’ve ever created. Feels amazing, and still so fast to iterate. Speed trumps many other considerations.

  • I release my cli app and people love it. Easy to distribute with goreleaser. Huge success!

  • Aside - Not a great experience in r/Zig. Very quiet in there. I spent a few days polishing up my hard won libraries (csv, unicode display width, natsort) and posted about it. For some reason they banned me for that, I doubt I will return.

Thanks all, looking forward to doing more with the language!

7 Likes

Reddit is not a great place for zig discussion. See the comments in Community ⚡ Zig Programming Language

5 Likes

Still don’t quite understand all the nuances of const vs. var, despite my lengthy experience with similar constructs across many languages. Eventually I tuned out and let the LLM handle it.

I’m somewhat curious about what issues you ran into here. I’ve found its mechanics to be super compatible with how it works in other languages. The most confusing thing I’ve encountered is how const works around pointers, but that is a struggle with any language with references (just try const a = {}; a.b = 6; in JavaScript), and it’s basically the same as C with the -Werror flag set.

I’d also suggest avoiding using an LLM in this situation, because it’ll extend the time you’re mental model is on shaky ground. I could yap more about this but it’d be unsolicited.

2 Likes

This is the reality of using a language/std/compiler that is still deciding how to do fundamental things, when zig is in less flux the docs will improve.

The langref and std docs are definitive (excluding a few TODO sections), and there are various community resources that are kept up to date.

There have been multiple attempts to use LLM’s for more docs, and they have all been disasters. That being said, those people didn’t know what they were doing, just AI bros who tried to pass of their flawed overconfident dumpster fire as their own innate genius.

The people who do know what they are doing are busy with more important things, they do help with docs but only up to “good enough” standards, which is a problem for people who less background knowledge, less experience navigating unknown areas.

std is mostly things the compiler needs, this is reasonable considering the stage of development zig is at. It is also arguable what is essential, that depends a lot on the software.

I don’t know why argv is in there? Do you mean arg parsing? That is actually planned, though it will be a simplistic parser.

Most likely just unfamiliarity with working at the level zig is at, the language and std.

Do not conflate error handling, and error reporting. zig errors are designed only for the former. Error reporting just adds a lot of complexity and is domain specific; I encourage you to look into patterns for this, I think you’ll find it a lot nicer than you think.

What issues did you have with memoization?

The premise of name conflicts is pretty simple, if its ambiguous what foo refers to then you can’t use that name. But since there are a variety of ways to refer to things, the specific rules can be confusing. Your difficulty is likely due to baggage from other languages, you’ll get used to it.

as others have said; I think you have a fundamental misconception, const/var is very simple and resorting to LLM for this is just ignoring the problem.

Andrew specifically didn’t make it a feature because a struct parameter fulfils the use case.

It’s all about zig caring more about ease of reading code over writing it. To be more specific:

Why can’t I globally import std

You could mean either “why isn’t std available without an import” or “why can’t I import everything within std, so I don’t have to std.foo
Both have the same answer: knowing where things come from is good. You can technically do the latter manually, but that is really annoying to write and read, so people don’t do it.

Where are my precious lambdas?

You can do them, its just manual! Partly because it gives you complete control, but also because callbacks can be messy so they are discouraged.

6 Likes

This was an intentional design decision (preventing python-like from module import * imports). It allows gaining more information from a single zig file, improving parallelism in the compiler and cache accuracy with less compute. Here is an article talking about it: Against Query Based Compilers

I don’t speak for everyone, but my interpretation of the zig community is one that values perfection, craft, simplicity over … idk shipping features quickly? We are also a smaller community, with few resources. The compiler team is < 10 people that are shipping industry-leading features every day, taking on challenges like “not relying on LLVM”. This is a colossal amount of work and yet they still deliver every day. No shortage of dreams yet they deliver. Not part of that dream I think is dealing with 100 “the LLM docs are wrong” issues on the issue tracker. Not part of that dream is unfulfilling work.

The docs will get better eventually, for me personally our biggest asset is this forum. This is why I post my simple questions, usually just answering it myself. Because if I can solve it now and somebody else searches for it, we all benefit. This is a simple decentralized way to move forward that doesn’t require Zig Core Team time.

And a bit of warning the ziggit community is very anti LLM, its viewed as an affront to the craft.

10 Likes

Thank you for the response, much appreciated! Really like zig so far, totally happy to spend more time building apps or contributing in other ways. I personally find postmortems useful so I figured I’d post it here. Just a few quick clarifications.

Re: docs. This is a deep topic. Even just improving the top few pages of ziglang.org would make a big difference, we could take cues from other doc sites. Even colors and font choices are important for adoption. Also see devdocs.io, etc.

Re: argv. You are correct - I am referring to cli arg processing. As a cli/tui app developer this is an instant source of friction and puts you right in the thick of it when you try to handle errors in a nice way. Juicy main should help, and great 3rd party libs, and some examples, etc etc.

Re: std. It would be nice if std and testing were auto-imported instead of requiring boilerplate in most/every file. Other languages have features like this, or even third party solutions that ease the boilerplate and/or linting burden. Good stuff, and Zig might benefit too.

I don’t think that this is a great idea and is suitable for zig because it prides itself on it’s explicitness. And that’s honestly why I like the language so much. A + is a plus and a << a bit shift and not something doing allocations concatenating strings or writing them to a stream.

It would also introduce more special cases into the compiler. And I’ve often heard the following reasoning: “so and so is already a special case, so why can’t we make this also a special case?”. And I, personally, just dislike this attitude.

Also creating a snippet for this is isn’t to hard. I for one have one that just prints this:

const std = @import("std");
const mem = std.mem;
const testing = std.testing;

const assert = std.debug.assert;

Invoking that is like 2s max. And writing it manually also doesn’t take more than a half a minute.

PS: Sometimes or often, depending on the codebase, you also don’t want to use (or can’t use) std so it’s nice to know that your code just depends on the language. So when/if, as I understood it, in the future the versioning of the standard library and the language may be decoupled, you can be sure that your code continues working when you update the standard library but not the language.

3 Likes

I’m curious what your experience in the last is with and what your project was.

IMO std should not be treated as special. it’s another module that can be imported. auto importing it would preference it too much.

3 Likes

Agree with @Calder-Ty and others - there are projects that do NOT want std (e.g., RTOS and baremetal) - there would have to be some place for projects to indicate that they want std or not (perhaps build.zig) - I’d much prefer that location be in the source files themselves so that the reader of your code can’t say, “what’s this foo()thing? Where does it even come from?” - it’s not even a question when the code reads std.foo() and you see std = @import(“std”) at the top. No magic. No wizardry. Everything out on the table.

2 Likes

I don’t speak for everyone, but my interpretation of the zig community is one that values perfection, craft, simplicity over … idk shipping features quickly? We are also a smaller community, with few resources. The compiler team is < 10 people that are shipping industry-leading features every day, taking on challenges like “not relying on LLVM”. This is a colossal amount of work and yet they still deliver every day. No shortage of dreams yet they deliver. Not part of that dream I think is dealing with 100 “the LLM docs are wrong” issues on the issue tracker. Not part of that dream is unfulfilling work.

I hear you and I agree. I’m def at the point in my career where fun/craftsmanship outweigh most other concerns. IMO there are a lot of ways to improve docs between “let’s do nothing” and “100 the docs are wrong” issues. Examples off the top of my head - iterate on the whitespace, typography and colors of the docs site. Improve the speed of the docs site. Link to the most popular doc topics at the top. Instructions and encouragement for people who want to contribute.

Probably not worth getting too deep into LLM etiquette here, I know this is a touchy topic… These days I like to pair with an LLM when learning in a new language, or working on boilerplate. I run all my work through both a human and LLM review (from a different LLM). An easy way to increase quality IMO.

As a simple example, my zig project has a lint-importsscript that verifies imports are placed at the bottom of each file and alphabetized. An LLM wrote the script, LLMs run the script after each change, and LLMs fix issues that arise. For my cli I have a similar script to make sure all cli args are present in --help, the README, man page, etc. Both scripts run alongside tests and during CI. Easy and fun! I’m not a tech bro.

1 Like

Related docs improvement thread: Documentation is the weak point of Zig – this problem must be solved

Yeah, Zig’s std makes much more sense when you think of it as “just enough to get the compiler/build system to work.” So no CSV (you can get pretty far with std.mem.tokenize* and/or std.Io.Reader.*Delimit*, unless your CSV files are particularly gnarly) or regex or Unicode beyond the basics. But on the other hand you get a functional HTTP client, and a bunch of hash functions, and also SemVer stuff.

The http client is needed for the package manager, the hash functions are needed for caching and hashmaps, and SemVer is the version system for zig packages (though it is not yet used to its fullest extent).

My point being that they are included in “just enough to get the compiler/build system to work”.

I don’t understand “Unicode width” comment. In general, you cannot get the display width of text without involving a font rendering system, which std will never have. Current stance on Unicode is that Zig std will not contain anything beyond encoding. If you need property tables, algorithms, etc. that will be community libraries.

My interpretation was that he wanted a utility that returned the number of “characters” in a unicode string (so compound symbols would only count one towards the total, etc.)

My interpretation was that he wanted a utility that returned the number of “characters” in a unicode string (so compound symbols would only count one towards the total, etc.)

Personally I think zig is well positioned to dominate the world of cli app development. It’s fun to write, easy to pick up, and churns out minuscule binaries.

If you want to align/center/justify in the terminal you will need to calculate the “display width” of strings that can contain emojis or other complex code points. This is required for truncation as well. I created a very basic one here, extracted from my tennis project - zig-atoms/unicode.zig at main · gurgeous/zig-atoms · GitHub

My point being that they are included in “just enough to get the compiler/build system to work”.

Mine too! :slight_smile: Sorry, I probably could have worded it better.

From what I’ve seen, the concept of “Unicode display width” can’t really exist until you have an extremely thorough model of the environment you’re in, a font rendering system as described earlier just being one way to get there. Here’s a pretty good (but a bit long and aging) read on the topic.

My personal concern with trying to do this is that, especially in a CLI, you don’t really know what the client you’re on looks like. To use the above link’s example, “:man_facepalming:t3:” could take up a width 1 because my terminal emulator assumes that’s what monospace means, 2 wide because it treats it as “full-width” like CJK, 4 wide because it doesn’t support skin tones in emoji (i.e. “:person_facepalming:🏼”), or even 5-6 width because it doesn’t support skin tones nor gender in emoji. It’s like tab-width concerns but x100.

To be fair, Unicode does specify some of these things in the form of grapheme clusters. Somehow, though, the terminal and the program have to communicate which version of the spec to use here, which at that point starts to feel like it needs a dedicated TUI frontend library rather than putting it in stdlib. I’m not a developer on the stdlib though so perhaps it is within their vision ¯\_(ツ)_/¯

1 Like

I’d like to think that terminals and fonts are gradually converging on a shared understanding of display width. Regardless, cli apps that attempt to do text layout must make the effort. That’s why you can find display width libraries in most popular languages. I suppose that includes Zig as well, now that I’ve published my (bad) implementation.

Please yap about it, as I’m interested to hear!