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?
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?
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.
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.
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.
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.
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
.
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
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)
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.
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
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.
You can use do as the label, if you like a do block.
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.
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?