Steering Zig Fmt

42 Likes

Nice! I wasn’t aware of the multi-item alignment feature at the end of the post.

Zig fmt formatted code is the only thing I’m religious about in this life.

19 Likes

I also like that zig fmt is tolerant of many alternative ways of formatting, but it still enforces some that I don’t like. For reference, I’ve been using my fork of zift (fork of asibahi/zift) that has a couple more of these tolerant alternatives (they’re described at the top of the wiki). Then there is one limitation in particular that makes me prefer my own fork over zig fmt (the braces style), I really can’t get used to those visually compressed conditional/loop blocks. Maybe zig fmt could increase its general formatting tolerance without introducing configurability.

5 Likes

Thanks fo that. That explains at least a bit of my struggle to generate zig source code that would not change when running zig fmt on it, after generation.

IMO the array example would be layed out more readable, with the comma’s aligned:

    .{
         1,   2,  3,
         4,   5,  6,
         7,   8,  9,
        10,  11,
    };

i.e. by first looking at the widest element for the whole array (and not trying to do this per per column).
I now guard such generated arrays from being reformatted with
// zig fmt: off
and
// zig fmt: on
.

1 Like

I never ever use zig fmt.
This is the first line in every of my zig files.

// zig fmt: off

The danger fmt messing up (mostly arrays, always logically aligned by me) is too big.

2 Likes

I’ve been writing Zig for over 2 years now and I just now discovered columnar layout :exploding_head: well done, @matklad!

3 Likes

Indeed, I was doing the same, but now I just never run zig fmt, mostly to protect custom formatting, but also to avoid the risk that code gets auto-upgraded without my input.

1 Like

i had too many experiences in vscode STILL formatting (on save or whenever secretly some setting was lost) so I keep it safe :slight_smile:

1 Like

The “choose how many columns you want in array formatting” feature was a life-saver when I was dealing with some source files full of big arrays of Unicode data.

I didn’t think I was gonna like a formatter better than rustfmt but zigfmt is definitely better. It’s also partly the Zig syntax that makes it so good. Some languages do not allow a (parameter) list ending with , which makes this kind of user defined formatting impossible.

I was never happy with rustfmt – my first formatter was IntelliJ, and it is also steerable, though not as crisply as Zig’s version: it generally looks at the presence of newlines, rather than trailing commas.

That’s what surprised me here: I’ve been thinking about formatters on and off for years, and this simple solution didn’t occur to me!

This is what I find to be uncanny about Zig in general — it always manages to find minimal solutions to familiar problems, and they have such a platonic quality that, once you’ve been exposed to the Zig way, you begin to question why anyone else doesn’t do this obvious thing! (see formatting, syntax for comments, syntax for strings, the way dependencies are identified by transitively pinned hash of directory tree, and, of course, using the same language for types and values, just calling stuff at comptime, rls-driven type inference without unification).

13 Likes

Absolutely! It’s easy to overlook it in the Zen of Zig but Avoid local maximums. just keeps paying off again and again.

6 Likes

I was initially very frustrated with the trailing comma thing but then quickly grew to appreciate it.

I also like that the formatter doesn’t care about screen width, and just asks you to break your lines yourself. It made it really trivial to fork it to indent with tabs.

I’ve been day dreaming about writing a similar spirited formatter for rust. But don’t feel like learning syn API

1 Like

In hindsight it has become quite obvious to me that rustfmt and quite frankly a few other things in Rust were never as good as I thought at the time (not that they were bad either). Most of that is seeing Zig just do things in a slightly different way that is oftentimes simpler but just as expressive. A part of it is also other languages catching up.

once you’ve been exposed to the Zig way, you begin to question why anyone else doesn’t do this obvious thing!

5 years ago I think the same thing could have been said about Rust as well! (To some extent this is probably still true for Rust but it’s probably already at the Late Majority in terms of the innovation/hype cycle so most potential users are probably aware by now). I wonder if in 5 years we’ll see another language make us again question everything we knew before.

One thing in particular that I find cool about Zig coming from Rust is how it manages to get most of Rust’s expressiveness by just tweaking C-style syntax instead of going a more functional route. For example in Rust everything is an expression which is super powerful. In Zig not everything is an expression but some critical things, like if and switch, can be used as expressions which basically narrows that gap completely for the vast majority of use cases I encounter.

1 Like

I might be mistaken, but I think the only thing which is not an expression in zig, and is expression in rust, is assignment? And IIRC there’s an accepted proposal to make it an expression as well. And there’s a whole bunch of stuff that is expression in Zig and not an expression in Rust: the language of type references, and the language of declarations!

The feel is different because, while block is an expression, it’s always void, and doesn’t have trailing expression. Which in Zig makes sense, because it doesn’t have lambdas. Though, I must say the idiom of b: { break :b result } bugs me due to made-up nature of name b… And there’s something non-orthogonal about where b: is allowed:

    _ = b: while (true) { break :b 1; };   // ok
    _ = b: switch (1) { 1 => break :b 1 }; // ok
    _ = b: if (true) { break :b 1; };      // err
4 Likes

unrelated, but this is possible

    const a = _: { break :_ 0; };
    std.debug.print("{}\n", .{a});

so maybe we could have something like this as shorthand?

    const a = :{ break: 0; };
    std.debug.print("{}\n", .{a});

or even dropping the : before the opening brace, so that it works with labeled loops too.

3 Likes

Ah right. I was more thinking of something like how you can omit return foo; in Rust and just write foo which is a cool feature and something I use all the time since it’s there but also not something I miss that much. I haven’t used Zig enough to run into that many edge cases but I did run into needing labels a few times in places where I would have liked to avoid it.

It’s a massive step up from other C-like languages where you often need to choose between making a variable mutable or using a lambda to get immutability.

The yield keyword from Hare would be nice.

I’ve come to appreciate having a soft limit of 80 columns in my code – narrow code feels slightly nicer to read to me. To that end, one more way to steer zig fmt I’ve recently learned about is adding trailing comments to lines EDIT[1]: newlines after operators.

E.g. I can take this long condition line:

                // ...

                if (self.flying_fruit == null or @reduce(.Or, axial.v != self.flying_fruit.?.v)) {
                    drawHexAlpha(
                        sizing,
                        .hexagon,
                        ticks.splash,
                        self.splashFrame(ticks.splash, axial),
                        axial,
                        global_alpha,
                    );

                    // ...

and split it up like this:

                // ...

                if (self.flying_fruit == null or
                    @reduce(.Or, axial.v != self.flying_fruit.?.v))
                {
                    drawHexAlpha(
                        sizing,
                        .hexagon,
                        ticks.splash,
                        self.splashFrame(ticks.splash, axial),
                        axial,
                        global_alpha,
                    );

                    // ...

Notice how zig fmt also pushes the prong’s opening brace onto an extra line, to keep the multi-line condition visually separated from the prong. Nice!


  1. Thanks @kristoff for pointing out that you don’t actually need the comments in this case ↩︎

1 Like