I make no judgement on your sensibilities of what is too verbose. Three lines of code seems little to provide for a language that embraces explicitness.
The range syntax is necessary to easily capture the index of the iteration. I personally consider for loop with nothing but a range a code smell.
We have anonymous blocks, so there’s no reason for any local state to pollute an outer scope. If you don’t use a block to isolate the index, that’s a valid choice, but if it isn’t the choice you intended, the fault does not lie with the language.
In some specific cases, the index is the only local state, and we’re used to that special case being handled in the C fashion. But in the general case, the index is not the only local state, nor are for
and while
loops the only reasons to isolate local state. So Zig has a general solution: blocks.
I don’t think anyone would invent the C three-statement for
loop if it didn’t already exist. Something being familiar doesn’t necessarily make it good.
Regarding repeatedly writing the same nested loops over x, y, z and frequently making mistakes with those, I wondered what optional iteration could achieve here:
var xyz: XYZ = .init;
while (xyz.next(chunk_size)) |i| {
the actual code... using i.x, i.y, i.z, all typed i32
}
In comparison to the classic nested while
loops, the optimized assembly looks similar, but not as straight forward (details). I did not investigate further after this quick experiment.
IMO turning nested for-loops into Python style itertools feels a little like bending backwards only to satify some abstract dislike for C-style for-loops which are just fine and readable to everyone.
I’m not really discussing in favour of the C for-loop, just that Zig shouldn’t be less convenient to write code for than C, and in many places Zig is much more convenient while also being more correct (which I guess is the important balance to strike - e.g. Rust is clearly too far in the ‘correct but very inconvenient’ corner, and for no good reason except maybe that the Rust language designers may subconsciouly think that correctness needs to come with suffering - which thinking about it is a surprisingly traditional Christian attitude for such a progressive community
Besides though, the C for-loop can manage multiple loop variables just fine:
for (uint16_t x = 0, y = 5, z = 10;
x < 10;
x +=1, y *= 2, z += 5) { ... }
…but as I said, I’m fine with dropping the C-style 3-part for-loops, but it would be nice if the replacements enable a similar feature set without having to compromise.
100% agree hahaha
I don’t think this is a useful terminal value. Specifically, C can be very convenient with carefully-chosen macros, but Zig doesn’t have those and I’m glad it doesn’t.
I also think this:
Is a bad feature from C which we shouldn’t carry over. It’s another case of false convenience, making code harder to understand for everyone in exchange for typing a few less letters while writing it. Forgivable in C, but bytes are simply not that expensive anymore and haven’t been for quite a long time.
I don’t think saving two braces and a level of indentation is worth complicating the language over.
It would also make scoping have an exception, since the clauses of a while
loop are not inside the braces which would denote the lifetime of the setup variable if that were allowed. I like as few exceptions as possible, they add up.
Funny you mention that because I would actually love to have 2-space-wide tabs in Zig because especially generic code is so ‘indentation heavy’, e.g. when you declare a generic struct with functions, all the implementation code is at least 3 tabs to the right (and that’s already with the ‘the file is the struct trick’ which saves one indendation leve …ok, not in this specific example):
pub fn Type(comptime cfg: TypeConfig) type {
return struct {
// ...
pub fn tick(self: *Self, in_bus: Bus) Bus {
var bus = in_bus;
// ...
// ...
// ...
// ...
}
// ...
}
}
In a ‘real-world’ source file this looks very ‘unbalanced’, since there’s way too much whitespace on the left, e.g. look at a file like this, scrolling to the middle of the file. The entire code looks like it’s floating in mid-air instead of being anchored to the left. Aesthetically quite unpleasant IMHO, but that might just be me
…and AFAIK it’s also not possible to move the implementation code out of the struct because it depends on the comptime generic params, and those are only visible inside the type-generation function
It’s a minor issue, but in similar indentation heavy languages (like Typescript) it’s not uncommon to use 2-space indentations.
Then again, I barely see any projects adopting this.
What I do see, however, is the dubious practice of reusing loop variables - plenty
of examples if you search a bit on sourcegraph. This is bound to introduce post-refactoring bugs.
A low-friction way to avoid polluting the scope when using while-loops is a good idea, alas, it’s been discussed and rejected here and here, so I think the best we’ll get is better formatting of scoped while loops.
Btw, C99 states the following rationale for adding this very feature:
A new feature of C99: It is common for a for loop to involve one or more counter variables which are initialized at the start of the loop and never used again after the loop. In C89 it was necessary to declare those variables at the start ofthe enclosing block with a subsequent risk of accidentally reusing them for some other purpose. It is now permitted to declare these variables as part of the forstatement itself. Such a loop variable is in a new scope, so it does not affect any other variable with the same name and is destroyed at the end of the loop, which can lead to possible optimizations.
The C community didn’t adopt the practice of “extra” scopes, and I somewhat doubt Zig will be any different.
You absolutely can do this, it’s just that zig fmt
won’t do it for you.
Writing your own formatter sounds like a ton of work, but cloning the stdlib formatter and changing the indentation depth? Maybe not so much.
I’m not trying to be dismissive (truly) I’m pointing to a fundamental tradeoff: a language can have a standard format or everyone gets the specific formatting they like the most. Those are, as I see it, the only options.
Other than the absolutely enormous diffs which would result (and subsequent pain using git blame
), I would have no strong opinion about zig fmt
changing to two spaces. I do prefer four, but my personal choice would be three.
What I like more than any of those is not having to think about it.
Tbh this might be a good reason to enforce \t
for indentation, even though personally I also use tabs-as-spaces in all my (non-Zig) projects.
This might also be an accessibility issue, I’ve heard of people who have readability problems even with 4-wide indentation.
PS: In general, I also see this as a good thing to have (standard conventions which are enforced).
I’m not a fan of too many customization options, with two very important exception: fonts and color themes, but I don’t think we’ll get to a point where a programming language will enforce those
But in a way, indentation width is also part of the ‘visual theme’.
The ‘accidental’ in that quote is pointing to C’s separate declaration and assignment system. Until C99, you had to declare all of your variables up front at the top of the block, such that accidental reuse (or a habit of reuse leading to accidents, more accurately) was a risk the language pointed directly at one’s foot.
Perhaps C99 should have only solved that problem, instead of that problem and a symptom of it?
Either way it’s not a problem we have, so we don’t need to solve it. Zig doesn’t work that way. It does have the option of assigning to undefined
but no one would write code that way as a matter of course.
So adding this would be solving a far less acute problem. Just out of curiosity, I searched for i = 0;
in all of my Zig codebases and found two examples, both in zg
(which I maintain but almost entirely did not write). Those were in a test, which is certainly defensible, but I cleaned up anyway, under the circumstances.
The only thing which makes this interesting (?) is that it was also reusing an iter
variable, which I also fixed with blocks. So having a special while
variant wouldn’t have helped much here.
This is something I do in tests fairly often, but in functions only infrequently, although it does happen sometimes.
I just don’t think there’s much of an actual problem to solve here. Encouraging people to reach for a scope if they find themselves resetting an index is not an intractable problem.
Well, Zig definitely works in a way where variables used in the loop condition pollutes the containing scope, which the C99 for-loop improves upon (though it has other problems)
We’ll find out, but so far it seems like people just don’t wanna do it despite knowing about it.
Are all cases of using a variable for part of a function, rather than all of it, ‘pollution’? Because that seems like special pleading.
Reusing a variable, right, that’s bad. But just, using one? I’m not seeing it.
How do you feel about reusing it by mistake? That’s what’s eventually going to happen if you don’t add the additional recommended scope (which barely anyone does, and we’re back to square one)
How likely is this to happen? Because it seems like several things have to go wrong.
var i: usize = 0;
while (...) {}
var i: usize = 0; // compiler yells at you
or
// no declaration of i
while (i < slice.len) {} // compiler will not accept this either
So you have to forget to declare your index variable, when you just happen to have an index in scope already? Because if you do it any other time, it won’t work.
Is this something we actually need to change the language over? It seems, minor.
Proper scoping doesn’t seem minor to me, it’s one more pitfal to worry about when refactoring. Small contrived examples isn’t really helpful… what if you pasted some code that originally used i
in a different context? Now it compiles when it shouldn’t. This is why I love Zig’s strict shadowing rules, it catches so many bugs. But sure, there’s a solution (extra scopes), let’s hope people get in the habit of using it!
I don’t think that you intend to argue that the compiler should somehow accommodate your copy/pasta workflow. But you might reconsider if this statement helps your point of view.