Format if condition multiline

Have you tried to use something like the feature that @kristoff has within helix, where the editor stacks the context as separate lines above where you currently are?

And would that help?

I don’t use helix but I guess I understood what you mean. I use something like that actually (but only for the top line of the scope). Problem is that this kind of editor features can become distracting, sometimes they even bug out, I prefer to be able to understand the code by just like… looking at it?

1 Like

I find Zig’s style readable, but I think that is different for everyone, here I would find projectional/structured editors useful, because they could theoretically show you the code in any way you would like to see it.

But building those editors and the tooling around them, comes with its own complexity problems, especially because text based editing and tooling has such dominance.

1 Like

With time, I decided for myself that less is more. Editor features make shiny toys, but the ones that really help me getting anywhere are the usual ones. For me it’s git and tags, the rest goes from entirely optional to entirely annoying. But of course it’s where I landed, other people may have different opinions.

1 Like

The label isn’t non-functional though, it lets you break from the block. Whether it’s an anonymous, for, while, switch, if block, you can put a label on it and break from it. Or continue, when that makes sense. Doesn’t matter how deep you are into the block either, if it’s got a label, you can use it.

It’s a simple and composable building block. I like those.

4 Likes

Not sure what you don’t like about labeled blocks. I’ve been using them as a perfect replacement for designated initializers and then some, having such things done at compile time is just great.

1 Like

I still think calling it 1-true-brace-style is hilariously arrogant, but I don’t think that was started by you.

Still, I would like to know who that was…

Anyway, I don’t think that saying one style should be used on the whole projects gives one the best outcome in all places.

For example if something is really big, I prefer to do this in C these days thanks to Zig’s formatting on functions when you use a trailing ,:

while (
	// multiple lines of conditions
) {
	// multiple lines of other stuff
}

This makes it very obvious where the separation between condition and body is.

But it has as a disadvantage that short ones become annoying to read, so I instead use something more akin to 1tbs:

while (/* cond */) {
	// a small amount of stuff
}

Which of course has a disadvantage of making the separation between condition and body harder to spot.

There are other style which tried to solve both problems like Horstmann:

while (/* cond */)
{	// stuff
}

or Pico:

while (/* cond */)
{	/* stuff */
	/* stuff */ }

But hey, I hope we can at least agree that GNU is weird:

while (/* cond */)
  {
    // stuff
  }

Yes, you are seeing half an indentation there.

The purpose of labels, or any name for that matter, is to let you distinguish one thing from another. In the typical usage scenario where you a block to initialize a const, there is no other thing. All the label does is let the parser to know you want a block. That’s why it ends up being something meaningless like ā€œblkā€ most of the time.

Using labels to break out of multiple scopes is getting real close to ā€œconsidered harmfulā€ territory. It’s basically like having a label at the bottom and going to it in C. It’s not a feature we want programmers to use regularly. Using blocks for initialization is generally a good practice on the other hand, since it let you keep intermediate variables in a separate scope. As it is, the level of friction is way too high.

I must disagree. When used, it is easier to understand than using helper variables like done.
Dogmatic ā€œGOTO considered harmfulā€ is considered harmful :wink:.
I think it is even less error-prone in Zig in comparison to other languages thanks to defer and errdefer.
For an example where breaking out of nested loops is reasonable, see Jumping Back and Forth Ā· Crafting Interpreters

3 Likes

Good point.

It’s the historically established name of that style ĀÆ\_(惄)_/ĀÆ (also I think it’s meant as a joke, but not sure of course)

1 Like

Back to the original topic, if I could choose, I would prefer a line breaking which reflects the precedence, like this:

if (    cond1a and cond1b
     or cond2a and cond2b
     or cond3a and cond3b
    ) {
    doSomething();
}

But this is probably out of scope for auto-formatting.
Then I’d prefer the logical operators at the beginning of the line.

Anyway, I usually just accept Zig’s auto-formatting, it’s perfect in most cases.

A bit OT, but complex logic could be written easier if the language would support and and or written as lazy-evaluated functions. But that wouldn’t fit Zig’s language philosophy.

Also - I’m not 100% sure - but I think GOTO considered harmful was written for unstructured GOTO like in BASIC. C’s goto has been tamed to work with the structured programming philosophy and provides an escape hatch if the other ā€˜structured gotos’ like break, continue or return don’t work, but it doesn’t allow randomly jumping around your entire code base like BASIC’s GOTO. The only thing to be aware of C’s goto is that it allows to skip over variable initializations.

2 Likes

Goto is fine too when used in the right circumstance and in an appropriate manner. At the minimal, your labels need to be somewhat descriptive. Yet in Zig, the use of short, meaningless labels has become completely normalized due to the lack of a dedicated block keyword.

Hahaaa :slight_smile: Yes the formatting wars always rage on whereever it pops up.

I don’t personally use meaningless labels like blk, basically ever. What I tend to do is use redundant labels, reusing the name of the identifier I’m assigning to from the block.

Which, granted, is also a bit unaesthetic. If you’re making a case for bare break from an unlabeled block, well, that’s different from a case against labels, isn’t it. I think it’s worth considering, at least for if expressions (not statements obviously).

Open question whether readability would suffer, and in general I don’t give a lot of weight to proposals which amount to making code look a little ā€˜nicer’ in an inescapably subjective way. I’m not averse to the idea, just not convinced that it’s especially important.

The labels themselves though, those I like. Especially labeled switch continue, without which I literally would not have picked up Zig as a language, since it would be unsuitable to my purposes without that construct.

1 Like

You can use do as the label, if you like a do block.

3 Likes

The use of labels is usually a sign that your function has grown way too large and ought to be refactored into multiple functions. A multiple level break, for example, can be avoided by moving the inner loop into a separate function. It can then break the outer loop by returning null or false. Smaller functions are easier to unit-test so it makes sense to encourage the practice by penalizing usage of labels with ugly syntax. The current level of friction is about right, I think. ā€œ:labelā€ is just hard to type.

What I just said applies to blocks too. The code in my previous post really ought to be a separate function:

fn shouldProceed(...) bool {
    // ...
    if (something_long) return true;
    if (something_other_long) return true;
    if (next_long) return true;
    return false;
}

Now the syntax looks normal and it’s easier to exhaustively test the logic.

Although this practice may be considered best practice in other languages, I don’t agree with it in Zig. I believe whether a piece of logic is suitable for being wrapped in a function should depend on whether it is truly a modular feature, not just on how complex its logic is. If a piece of logic is really complex, wrapping it in layers of functions only hides its complexity beneath the surface, forcing the reader to jump through layers instead of immediately reading them. More often than not, I prefer to understand the logic immediately in-place while reading Zig code, rather than hiding its actual implementation in a function and relying on the compiler to decide whether it gets inlined (as far as I know, the current LLVM actually doesn’t take the inline keyword seriously). This is especially true when that logic is only implemented in one function.
I believe many languages view this practice of hiding implementation details in functions as best practice simply because they don’t have a safe structured goto, making functions the only valid and reliable way to encapsulate.

As a supplement, I want to emphasize a category of functions that I completely do not agree to be separated: coupled functions. These functions can essentially only be called by another specific function, and beyond that, they have no independent meaning for being called by other functions.In my view, splitting out these purely coupled functions, which essentially have no modular value, just to reduce the complexity of a function is very bad. Labeled code blocks should be their rightful place.

6 Likes

If a piece of code is untested, then you have no assurance that it does what it’s supposed to do. What use is it to see the code?

1 Like