Hacker News new | ask | show | jobs
by pron 1999 days ago
> and I find it's much easier to forget the ++i at the end of the while loop.

You can do:

    var i: usize = 0;
    while (i < 10) : (i += 1) {
        ...
    }
or

    var i: usize = 0;
    while (i < 10) {
        defer i += 1;
        ...
    }
although the two have slightly different semantics (the latter would increment even after a break; the former wouldn't).
3 comments

Given how Zig does not allow variable shadowing (which is a choice which makes sense on its own) I find the scope leak to be very annoying in practice.
Neither of those fix the scope leak, tho.
This is true. There have been proposals to alter the `while` syntax to fix the scope leak, as well as proposals to extend `for` to support ranges, but none have been accepted (and they aren't very compelling in my opinion).

Honestly, I'm not sure why people think it is such a big deal. I've written a lot of zig, mostly gamedev stuff, and I rarely use that construction. I find that the vast majority of loops are over arrays or slices.

While I don't have much experience with Zig, I agree -- from my little bit of playing around, it also hasn't been a big deal.

(The biggest thing I've hit that I want is a way to return information with an error. If that were fixed, I think Zig's error handling would be perfect.)

Loris Cro did a great talk on error handling in Zig, which also deals with how to return information with an error if you need to, and why the common case optimizes for simpler error values: https://www.youtube.com/watch?v=TOIYyTacInM
Thanks, that was a nice talk.

But the answer there is basically, "if you care about payload, don't use zig's built-in error handling." Which, obviously, you can do, but I'm not convinced it's a great answer!

Even for something as simple as parseInt, it would be nice, I would argue, if it returned, along with InvalidCharacter, which character was invalid. This would enable e.g. very precise indications in messages to the end user of what was wrong with the input.

In general, because currently it's super-ergonomic to return just error codes but more of a pain to return errors w/ payloads, what you'll get in practice is no error payload even when it would be useful, which will end up turning into less-helpful-than-ideal errors for end users, which is contrary to zig's goal of enabling the creation of perfect software. :-)

I found this thread interesting

https://github.com/ziglang/zig/issues/2647

Thanks for the link to that proposal.

Just thinking out loud, given Zig's errors primarily allow the compiler to enforce that errors are handled, and exhaustively, beyond that why can't error payloads just be passed as an input argument pointer to the function call? Maybe this is a dumb idea, but it's what C programs already do, except C programs don't have the safety of compiler checked errors offered by Zig. Does the Zig compiler need to check error payloads beyond the type system already provided? I'm not sure.

I agree with you that having such a super-ergonomic error handling system will direct most code towards a certain style, but I don't think this is contrary to Zig's goals of perfect software or simplicity. It's already such a huge win on top of C's error handling.

To me the scope leak was annoying because how Zig does not allow shadowing, so I have to write:

  var it = a.getSomeIterable();
  while (it.next()) |item| {
      ...
  }

  var it2 = b.getSomeIterable();
  while (it2.next()) |item| {
      ...
  }
You can do:

  {
    var it = a.getSomeIterable();
    while (it.next()) |item| {
        ...
    }
  }
  {
    var it = b.getSomeIterable();
    while (it.next()) |item| {
        ...
    }
  }
Yeah, it's not that common in general, I agree. I think my impression was coloured by the fact I tried Zig to implement a binary protocol deserialiser where getting the size first then iterating happened a lot.
The pattern I use for that is:

  get size
  allocate slice
  for(slice) |*elem| elem.* = read()
Both forms feature "implicit flow of control", contrary to Zig's stated goals. In "while (i < 10) : (i += 1)", it's hard to understand what the heck "(i += 1)". The best concept one might get is that it's puposely made be different from C, just to be different and confuse people.

In second case, it's "defer", coming from Go, the language which chickened out to add normal exceptions, because they're "implicit transfer of control", and LOLishly added "defer", as if it's not such.

> Both forms feature "implicit flow of control", contrary to Zig's stated goals.

They absolutely don't. All control paths are explicitly represented by syntax (no different from the hidden goto in a while loop -- it's explicitly recognised by the while syntax); proof: you can draw all of them by just examining the syntax of the current subroutine, while knowing nothing about others. Exceptions, however, are implicit: any call, foo(), might or might not cause some control flow change in the client without there being any explicit acknowledgement of that by the client; you cannot draw all the flow paths just by examining the syntax of the current subroutine.

Sorry, but throwing an exception in a function call is equivalent to:

    res, exc = fun();
    if (exc) goto exception_handler;
That's underlying model of how exceptions behave, and how they're implemented "manually" in languages with no exception handling (C, Go). You absolutely can draw that by examining syntax of a subroutine, and it's no more implicit than "defer".
Right, both the C style and Zig's defer are explicit, as opposed to exceptions, which are implicit, only Zig's error handling is less error-prone (it forces you to handle errors) and makes the code more readable, IMO, than the C style.
But no, there's a kind of continuum, and Go/Zig "defer" is already pretty high towards "implicit flow of control" end. (I agree that exceptions are a notch higher.)

It's only C's syntax which is truly explicit. It's literally a structured machine-independent assembler. That's why it's gold language which is very hard to displace (it's already perfect for what it is). But just as everyone I'm watching with popcorn all the contenders popping up. (My humble opinion about Zig's issues on that path, I, together with other people, expressed here.)

> is already pretty high towards "implicit flow of control" end

Its implicitness is zero -- there is zero information not available in the syntax of the current block, exactly as in C -- so I don't see how it can be high compared to anything. You just don't have it in C, so you're not used to it. This is exactly like an Assembly programmer who says that C's `while` is implicit because there is no explicit jmp. In fact, the third clause in C's `for` header works almost exactly like defer: you write a piece of code that isn't executed immediately after the preceding one, but is injected to the end of the block.

A language with `while` isn't any more implicit than a language with just goto; it just has another explicit control-flow construct. Same goes for defer.

> It's literally a structured machine-independent assembler.

Not so much once you take the preprocessor into account.

> it's already perfect for what it is

I think that was true in 1970, not today. First, we know more. Second, we write much bigger programs. Third, "structured assembly" isn't as valuable as it was now that even machine code is "high level." This is not to say that C could be replaced everywhere, but I think, intertia aside, it could be replaced almost everywhere.