39 ms·
Zig's multi-sequence for loops
- carterschonwald 4y agoZipwith is a great iteration api to have available.
- Y_Y 4y agoIs this different from good old `zip`?
- Yoric 4y agoApparently, it's zip, except with an UB if sizes don't match.
- helloworld23443 4y agoHilarious. I was evaluating Zig, I took a look at Bun, probably the most well known Zig project. Multiple issues related to seg faults.
- saagarjha 4y agoThose are safe depending on what kind they are.
- kristoff_it 4y agoTake a look at TigerBeetle which is also written in Zig, if you find segfaults there they even give you money :^) https://github.com/tigerbeetledb/tigerbeetle https://github.com/tigerbeetledb/tigerbeetle
- rosetremiere 4y agoIt's hard to understand what tigerbeetle is about. Can anyone ELI5 it for me? As far as I can tell, it's some kind of a library/system geared at distributed transactions? But is it a blockchain, a db, a program ? (I did look at the website)
- deleted 4y ago[deleted]
- laserbeam 4y agoIt's a special purpose DB. No relation to blockchains.
- eatonphil 4y agoHey thanks for the feedback! We've got concrete code samples in the README as well [0] that might be more clear? It's a distributed database for tracking accounts and transfers of amounts of "thing"s between accounts (currency is one example of a "thing"). You might also be interested in our FAQ on why someone would want this [1]. [0] https://github.com/tigerbeetledb/tigerbeetle#quickstart https://github.com/tigerbeetledb/tigerbeetle#quickstart [1] https://docs.tigerbeetle.com/FAQ/#why-would-i-want-a-dedicated-distributed-database-for-accounting https://docs.tigerbeetle.com/FAQ/#why-would-i-want-a-dedicat...
- rosetremiere 4y agoThe faq helped, thanks! So, an example of typical use would be, say, as the internal ledger for a company like (transfer)wise, with lots of money moving around between accounts? But I understand it's meant to be used internally to an entity, with all nodes in your system trusted, and not as a mean to deal with transactions from one party to another, right?
- eatonphil 4y agoYes that's a good example! And you can model external accounts that have their own confirmation process using our two-phase transfer support. https://docs.tigerbeetle.com/FAQ#what-is-two-phase-commit https://docs.tigerbeetle.com/FAQ#what-is-two-phase-commit
- jorangreef 4y agoGreat to hear! Joran from TigerBeetle here. Yes, exactly. You can think of TigerBeetle as your internal ledger database, where perhaps in the past you might have had to DIY your own ledger with 10 KLOC around SQL. And to add to what Phil said, you can also use TigerBeetle to track transactions with other parties, since we validate all user data in the transaction—there are only a handful of fields when it comes to double-entry and two-phase transfers between entities running different tech stacks. The TigerBeetle account/transfer format is meant to be simple to parse, and if you can find user data that would break our state machine, then it's a bug. Happy to answer more questions!
- speed_spread 4y agoWell, Zig would not be such a good C replacement if it did not also allow segfaulting all over the place.
- jeroenhd 4y agoLooking at the bugs themselves, I don't think any low level language would've caught those. C/C++ would've crashed as well (hopefully, at least, many of these problems would be UB and the compiler might just ignore the problem or patch out the offending code) and Rust would've panic'd. There are a few cases where Rust wouldn't have allowed the code to panic but the surrounding code would be pretty unreadable in safe Rust (without stacking types like Box+RefCell+Rc and clone()ing a bunch) so it's hard to compare the two. The advantage of Rust would be a nice and readable stack trace to the crashing method, but a core dump would've included even more information for the person debugging the binary, so I think it ends up quite even.
- cormacrelf 4y agoI don’t know what’s going on in this thread where encountering UB has somehow been morphed into some kind of guaranteed immediate core dump that’s basically better than panicking anyway. Yes, people are talking about segfaults. But it’s memory corruption. Maybe you get a crash at some point, maybe you do not. A reminder for all that have forgotten: UB is the one that can email your local council and submit a request to bulldoze the house you’re in. It is not a free core dump.
- sfink 4y agoYou appear to have particularly vengeful nasal demons.
- AndyKelley 4y agoA sementation fault is well-defined behavior. If you look at Jarred's comment nearby he reveals that the pointers in question are special pointers, e.g. 0x0, 0x1, 0x2, etc. It is 100% well-defined behavior to dereference these pointers. It always segfaults, which as Jarred mentioned is a lot like a panic. Rust evangelists need to be careful because in their zeal they have started to cause subtle errors in the general knowledge of how computers work in young people's minds. Ironically it's a form of memory corruption.
- 4y ago
- Jarred 4y agoMost of what manifests as a segfault in Bun have been due to assuming a JSValue is a heap allocated value when it is (the JavaScript representation) “null”, “undefined”, “true”, “false” etc. These are invalid pointers, the operating system signals the memory access was invalid, Bun runs the signal handler, prints some metadata, and exits. This is a lot like a panic
- naikrovek 4y agosafety-checked UB; an important distinction. I assume.
- planede 4y agoNot very. It really just means that "there is a sanitized mode for building", which already exists in many C and C++ compilers (for language UB) and standard libraries (for library UB).
- naikrovek 4y agoI think of this case as an assert() that both lists are the same length. if they're not, I want to know about that via a crash.
- oconnor663 4y agoI think Zig's ReleaseSafe mode is intended to be suitable for production, which IIUC isn't really the case with ASan, UBSan, and friends. Those have some performance problems and also some attack surface problems.
- planede 4y agoOK, but is "safe" in ReleaseSafe any kind of guarantee, or is it just safer than ReleaseFast? I can enable lightweight assertions in libstdc++ and libc++ and it makes C++ safer, but not in any way "safe". There are some flags that can be enabled to trap on some language UB too, without bringing in the heavy weight sanitizers.
- oconnor663 4y agoLast time I checked (more than a year ago) there were major open questions about what could be guaranteed. My impression was that you could expect e.g. all array accesses to be bounds checked, but that use-after-free and dangling pointers were still issues, especially if you use the C allocator.
- 4y ago
- randyrand 4y agoIs it UB? I thought it was defined as an OOB access in non-safe modes, or a panic in safe-modes.
- jeroenhd 4y agoIt's zip with an arbitrary amount of array parameters. Quite useful for the purposes provided by the article. I do wonder about performance, though, as multiple array derefences may not be captured well by the L1 cache like a well-rounded struct might. An L1 cache line is often 64 bytes long, enough to fit one of the "monster" example structs but never two. Performance in real life scenarios may actually increase if these structs are padded with an additional 16 bytes so none of the structs are on a cache line boundary.
- masklinn 4y ago> It's zip with an arbitrary amount of array parameters. So… zip? Python’s does that, and for most other langages you could use overloading, basic macros, or traits trickery to get there if you really wanted to support unreasonable widths (IME you almost never need more than 3, and combining two zip/2 works fine then).
- klyrs 4y agoZig's "zip" is purely syntactical, where Python's zip is a generator. This is significant both in terms of performance (Zig wins) and flexibility (Python wins). Unlike Python, you can't pass a zip generator around. It's just a for-loop. While zig loops are expressions, they only return a single value.
- masklinn 4y ago> This is significant both in terms of performance (Zig wins) Does it, actually? Does Zig's built-in pseudo-zip outperform Rust's? Or C++23's? > It's just a for-loop. Except it's not "just" a for loop, it's a weird special case for a for loop. And one which is actively dangerous too.
- klyrs 4y agoPlease check the context, I was comparing to Python. And please chill out. It's only "dangerous" when you decide to run it that way.
- Laremere 4y agoAs much as a concept can be translated from one programming language to the next, they're conceptually pretty much identical. However for Zig there are two important differences: 1. It's simpler syntax than reaching for a zip function. I personally like this design because the conceptual load is pretty low as it feels like a natural extension of simple for loops. Eg you could teach someone simple for loops, then later go "hey, you could do this the whole time!" 2. Zig doesn't have support for custom iterators. Zip is doable using the existing metaprogramming features, but it's not as simple. Support for iterators also likely violates Zig's `No hidden control flow.` maxim. Plus I imagine it's a lot easier for the compiler to perform optimizations this way. Both points combined are related to Zig's design goal for being good at writing code that can run fast on modern CPU architectures. Being able easily to loop over multiple arrays is a good step for making that practical.
- Jayschwa 4y agoWhile with optionals enables some iterator-like behavior. https://ziglang.org/documentation/master/#while-with-Optionals https://ziglang.org/documentation/master/#while-with-Optiona...
- malcolmstill 4y agoTo expand on the parent and grandparent: Laremere is correct in that there is no "magic" built-in understanding of iterators in the language, i.e. under the hood calling a `.next()` method, without explicitly having to call it. That _would_ violate the no hidden flow control maxim. However, as Jayschwa points out, Zig's `while` loop will bind the result of its expression (in its own block scope) if it is non-null and otherwise exit the loop. This gives you essentially the same as a for loop that has some language-level knowledge of the iterator pattern, except there is no hidden flow control (I have to explicitly call `next`). And indeed the Zig standard library is replete with iterators (and in most of the Zig code I write I will will write iterators for my own collections). For example, `mem.split` returns an iterator: var it = mem.split(...); // it.next() returns null after we run out of // split text and the while loop exits while (it.next()) |substr| { // In here we have a non-nil substr } > Plus I imagine it's a lot easier for the compiler to perform optimizations this way. That's an interesting point: does Zig miss out on some optimisation possibilities with iterators given they are not a language-level construct? I don't know.
- AnIdiotOnTheNet 4y agoRanges for for loops was a long time coming. Some C programmers just completely lost their shit at having to use a while loop for some reason. It was such a common complaint that we invented stuff like the following as a joke, that of course became something people actually used because internet gonna internet: for(@as([10]void, undefined)) |_, idx| { _ = idx; } Which, for those unfamiliar with Zig, has `for` iterate over an 'array' of 0-bit values and capture the index while throwing out the value (which would always be 0 because that's the only value a 0-bit type can represent). The implementation of this proposal brings with it the additional benefit of making index capturing less mysterious.
- gonzus 4y agoI suspect the real reason behind "some C programmers losing their shit" was being forced to introduce a new variable into the outer scope, or to use extra braces around your loop -- both are distasteful hacks, according to some.
- titzer 4y agoRegarding the lengths must match: > (i.e. you will get a panic in safe release modes) Should I take that to mean there is an unsafe release mode without the bounds check? But UB is mentioned too. Is there UB!? It's 2023; I think we can afford a single branch to avoid UB, even in release mode.
- conradev 4y agoit is one of the build modes: https://ziglang.org/documentation/master/#Build-Mode https://ziglang.org/documentation/master/#Build-Mode
- titzer 4y agoInteresting, thanks for the link. I think that disabling safety checks is a thing you should only do if you are studying the cost of safety checks (i.e. a compiler switch only available to compiler engineers). IMHO, the whole point of safety checks is to find the bugs that are in your program[1]. Crashing it safely with an exact source stack trace is the nice way of both motivating you to fix it and also helping you fix it. [1] And there are bugs in your program. Right now. Bugs. In. Your. Program. Running without safety checks is like closing your eyes and rolling the dice.
- quic5 4y agoThere's also the compromise of only disabling safety checks per block e.g. in your hot loop with `@setRuntimeSafety`[1] where you are confident that they aren't needed. [1] https://ziglang.org/documentation/0.10.1/#setRuntimeSafety https://ziglang.org/documentation/0.10.1/#setRuntimeSafety
- laserbeam 4y agoMost software should probably release with safety checks on. Certain software shouldn't (i.e. games). Toolchains like zig give you that option and respect that you can decide what's most appropriate for whatever you ship. Arguing that safety checks should always be enabled doesn't really make sense. Context matters.
- MrBuddyCasino 4y agoIs must say this looks pleasant, coming from Kotlin. Also ranges seem to work very similarly. Not sure how I fell about the UB - is it really necessary to optimise away a single length check per loop (not iteration)?
- AnIdiotOnTheNet 4y agoThat's up to the programmer. Zig's default build mode is Debug, and ReleaseSafe is recommended if you don't require extreme performance. Both modes will insert the check. Safety checks can also be enabled or disabled on a scope-by-scope basis if desired.
- hota_mazi 4y agoQuestion for Zig experts: for (elems) |x| { std.debug.print("{} ", .{x}); } Why is the .{x} necessary here? What happens if I just write "x"?
- anonymoushn 4y ago`std.debug.print` and similar fmt-like functions take 2 arguments. The first argument is a format string, and the second argument is a tuple. I think a tuple is an anonymous struct whose members are named 0, 1, 2, etc., but I'm not completely sure on this. If you just write "x", it won't work, since you needed to pass a tuple containing 1 thing, and x probably isn't a tuple containing 1 thing.
- quic5 4y agoThe print function is implemented in the std library[1] not the compiler and Zig does not have varargs [1] https://github.com/ziglang/zig/blob/f6c934677315665c140151b8dd28a56f948205e2/lib/std/debug.zig#L89 https://github.com/ziglang/zig/blob/f6c934677315665c140151b8...
- AnIdiotOnTheNet 4y agoZig doesn't have varargs anymore. Instead, it has anonymous structs/arrays/tuples. The second argument to `print` here is expected to be a list of the values referenced by the `{}` placeholders in the string in the first argument. `.{a, b, c}` is the syntax for an anonymous struct/array/tuple, and a single element still needs to be wrapped in it.
- Laremere 4y agoZig doesn't have variable length args. The .{} syntax is for an anonymous struct with no field names (which are named tuples in Zig.) Print takes the struct's type info at compile time to check the validity of the statement, and also produce optimal code. This is implemented entirely within Zig's standard functionality that's available to all users. So, if you just type X, you're getting an error about it not being a struct. That's unless X is a struct with one field, where it'll just print that field. I find Zig meta-programming to actually be fairly readable, here's the function that does the formatting: https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig
- _7bxa 4y agoI really like that for loops can be expressions. It seems obvious in hindsight, but hindsight is always 20/20 :)
- masklinn 4y ago> It seems obvious in hindsight It's not, because most languages don't have an `else` clause in their for loop (and in my experience with Python that clause is quite confusing so its use is not common). And a for loop can be executed 0 times, so without a mechanism for a fallback it might not have a value to yield.
- Someone 4y ago> And a for loop can be executed 0 times, so without a mechanism for a fallback it might not have a value to yield. I would think that and the similar case where no iteration hits break are solvable by having a for loop return an optional type.
- bjoli 4y agoI think a language where it is an expression should also let the loop accumulate a value. In those cases you could just have it return the identity/base type of whatever you are accumulating. Say like the for loops in racket, or my own loops for guile scheme: https://git.sr.ht/%7Ebjoli/goof-loop https://git.sr.ht/%7Ebjoli/goof-loop
- rcme 4y agoThis kind of syntactic sugar used to appeal to me, but now I think it's a pretty weird feature to add to a language. Using zip / enumerate primitives feels a lot more flexible.
- laserbeam 4y agoDepends on what you mean by flexible. If you want to use them outside of loops then they could cause magic data copies behind the hood. Zig really hates hidden control flow/allocations/copies. Within the for syntax it's pretty straightforward what gets assigned to what variables and how copies can be avoided. Doing things like `a = @zip(some_list, some_other_list)` can be reasoned about in multiple ways, some of which involve silently calling malloc. It's particularly unclear what could be done with `a` afterwards. Zig hates that kind of ambiguity and is happy to err away from flexibility at times.
- brundolf 4y agoRust also hates hidden allocations, and its iterator system can do all of this without them Although- thinking about it, that may rely on the borrow checker (move semantics specifically)
- masklinn 4y agoThat's nothing special though, `zip` just takes an item from each iterator, packs them into a tuple, and yields that. It has no weird bounds or requirements or anything: https://doc.rust-lang.org/std/iter/fn.zip.html https://doc.rust-lang.org/std/iter/fn.zip.html The impl of the default `next` is: fn next(&mut self) -> Option<(A::Item, B::Item)> { let x = self.a.next()?; let y = self.b.next()?; Some((x, y)) } So completely straightforward.
- brundolf 4y agoI was thinking about the fact that whatever you're iterating over has to be copied around throughout the process. Rust can guarantee that eg. deep-copies (clones) of allocated structs will never happen implicitly, if your iterator owns the values being iterated. But in languages where copying can trigger allocations, this could be a problem I don't actually know whether that applies to Zig though
- Existenceblinks 4y agoProbably a good signal for potential O(n^2) when reading the code. EDIT: Nope. I was wrong. This is not a list comprehension.
- loeg 4y agoWhy do you say so? It's equivalent to iterating indexes from 0 to N-1 of two (or more) lists with N elements and providing syntax sugar for those lists' elements at that index. This is O(N).
- Existenceblinks 4y agoTrue. I misread it, it's a zip pattern. I thought it was a fancy list comprehension.
- tuukkah 4y agoRight, it's not an ordinary list comprehension. It's a parallel list comprehension though: zip as bs = [(a,b) | a <- as | b <- bs] https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/parallel_list_comprehensions.html https://downloads.haskell.org/ghc/latest/docs/users_guide/ex...
- Kukumber 4y agoKinda nice to have, D can do it aswell: import std; void main() { int[] a = [1, 2, 3]; string[] b = ["a", "b", "c"]; foreach (e1, e2; zip(a, b)) { writeln(e1, ":", e2); } } 1:a 2:b 3:c
- arethuza 4y agoI was fond of the Common Lisp loop macro that handled iterating over multiple things quite nicely: https://lispcookbook.github.io/cl-cookbook/iteration.html#loop-1 https://lispcookbook.github.io/cl-cookbook/iteration.html#lo... Edit: 27 years since I was paid to write Lisp....
- masklinn 4y agoThis is very strange, because it looks like a comprehension (https://wiki.haskell.org/List_comprehension https://wiki.haskell.org/List_comprehension), which would be a product iteration. Most languages have a function called zip or something similar (https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html#v:zip https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelu...) which handles pairing sequences, to be composed upstream of the iteration proper.
- tuukkah 4y agoIt's a parallel list comprehension, as linked from the wiki page you referenced: https://downloads.haskell.org/ghc/9.4.4/docs/users_guide/exts/parallel_list_comprehensions.html https://downloads.haskell.org/ghc/9.4.4/docs/users_guide/ext...
- taeric 4y agoPretty sure it predates most of the "similar" things mentioned. :)
- bjoli 4y agoI wrote a looping facility for guile scheme recently, which was somewhat inspired by common lisps other big looping macro Iterate: https://git.sr.ht/%7Ebjoli/goof-loop https://git.sr.ht/%7Ebjoli/goof-loop scroll down for examples. It is more limited than the one in common lisp, since it turns all loops into tail recursion and this uses no mutation outside the higher-order looping constructs. It is written mostly using the hygienic syntax-rules facility (yes, it actually works) and I haven't managed to get it to produce slow code yet.
- senkora 4y agoNit: It should be “Air Nomads” instead of “Wind Nomads”. (I know this doesn’t matter but I figured the author would appreciate the heads up!)
- kristoff_it 4y agothanks, fixed, I started by thinking about the last example (the one about pokemons) and then it stuck
- Decabytes 4y agoI've been curious about Zig. I find its cross compilation story using zig cc interesting. I like its focus on simplicity instead of debugging your knowledge of the language. On the surface it looks like a better C, that isn't as complicated as Rust. I'll admit though the syntax is a little off putting. But that is a minor complaint. I know it's not 1.0 and there are still lots to do, but I'm curious if they do more for memory safety. With companies trying to avoid starting new code in memory unsafe languages if they don't have to , I wonder if that will hurt Zig adoption. Right now it just seems like their approach is, reduce Undefined Behavior as much as possible, make the "correct" way of programming the easiest, and have array bounds on by default. Will this be enough to make the language "memory safe" enough?
- throwawaymaths 4y agoIf zig includes tags or annotations (there are a few proposals in the issues tracker) and surfaces this information at an exportable zir level, it seems likely that data provenance tracking (this includes memory safety and file descriptor, socket closing, thread spawn/despawn etc) would be able to be checked by a static analysis system. If zig supports compiler hooks, then it could conceivably halt compilation unless these invariants are satisfied. I'm not convinced that this needs to be in the type system. Nonetheless it's not there yet.
- matu3ba 4y ago> If zig supports compiler hooks, then it could conceivably halt compilation unless these invariants are satisfied. Can you sketch out why hooks are necessary on the proposals, like 14656 ? So far I have not read a justification, what the advantage of coupling it to the compiler provides as performance gains on concrete examples. Afaik, Rust has lifetime checks decoupled to parallelize compilation and I have not seen research or a list of the possible optimizations with synthetic benchmark results. > I'm not convinced that this needs to be in the type system. If you want to prevent static analysis semantics becoming an incompatibilty mess like C due to 1. unknown and 2. untrackable tag semantics, then you have to provide a at least an AST to ensure annotations have known + unique semantics as I explain in https://github.com/ziglang/zig/issues/14656#issuecomment-1437639789 https://github.com/ziglang/zig/issues/14656#issuecomment-143.... I would say that this is a primitive quasi-type check decoupled from main compilation and could be kept very flexible for research experiments by having an uri + multihash (so basically what Zig uses as package). More concretely: Stuff to look out for is RefinedC and related projects to have more strict C semantics + how to compose those things outside the regular Zig type system.
- planede 4y agoIn C++23 with zip it looks something like: for (const auto& [x, y] = std::views::zip(a, b)) { /* ... */ } A notable difference is that the ranges don't have to match in size, the loop will run until the end of the shorter range is reached. If it is required for optimization to not check for reaching the end of one of the ranges then it can be achieved with something like: for (const auto& [x, y] = std::views::zip(a, std::ranges::subrange(std::ranges::begin(b), std::unreachable_sentinel))) { /*...*/ } I guess it's hard enough to bump into this by accident.
- edflsafoiewq 4y agoIn Common Lisp it's (loop for x in a ; on each iteration, steps x to next element of a for y in b ; same thing do ...) This is like the Zig in that it's a hard-coded feature of the looping construct instead of being a general combinator like C++/Rust, but I think it's neat that by allowing multiple clauses, zipping falls out completely for free.
- Someone 4y ago> This is like the Zig in that it's a hard-coded feature of the looping construct I think lisp’s loop isn’t hard-coded in the language, but defined in the standard library. See for example the loop implementation at https://github.com/sbcl/sbcl/blob/master/src/code/loop.lisp https://github.com/sbcl/sbcl/blob/master/src/code/loop.lisp (about 2,000 lines because loop is a monster/very flexible (pick whatever you prefer. I would pick both))
- edflsafoiewq 4y agoLOOP isn't hard-coded into the language, but the possible clauses are hard-coded into LOOP. This is in contrast to ITERATE, which is an extensible CL looping macro, or the generator/iterator style popular in C++/Rust/Python/etc.
- kazinator 4y agoIn less than 400 lines of C99 preprocessing (not counting some utilities included), I made a loop macro for Awk which supports parallel and nested (cross-product) iteration, and user-definable clauses. https://www.kylheku.com/cgit/cppawk/tree/share/cppawk/include/iter.h https://www.kylheku.com/cgit/cppawk/tree/share/cppawk/includ... The bulk of the file is defining the clauses, each of which requires six lines: six simple macros have to be defined. (Kind of reminiscent of the five SETF expanders in Common Lisp for defining a new place.) This is thoroughly documented by a man page, including user-defined clauses. An complete example is given how to make a cause which iterates over alphanumeric strings, as in: BEGIN { loop (alpha_range (x, "aa0", "cc9")) print x } where x is stepped over "aa0", "aa1", ..., "aa9", ... "ab0", ..., "cc9".
- dan00 4y agoI think the naming of the ‘else’ branch in the loop could be more telling, like using the name ‘finally’ or ‘finish’.
- Someone 4y agoI agree, but ‘finally’ or ‘finish’ IMO aren’t good choice because that code doesn’t always execute. I think I would go for something expressing ‘default’, but would first look at existing code to see how common this is, and if I decided I wanted this feature, look hard for alternative syntax. const match: ?usize = for (text, 0..) |x, idx| { if (x == needle) break idx; } could return an optional int, for example. If so, you would get a ‘null’ for free, and if you didn’t want a null, you could tack on a .getOrElse(NOT_FOUND). I guess they picked this because Python has it, too. https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops https://docs.python.org/3/tutorial/controlflow.html#break-an...: “Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the iterable (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement.”
- masklinn 4y ago`finally` hints at a very different behaviour because in most languages' context a finally clause is executed whether an exception is raised or not.
- spullara 4y agoI'm not sure zip is used enough to add it to the language but since they are also using it for tracking the index maybe that is the primary use case.
- kristoff_it 4y agoThe example of using SoA memory layout is not there just as a random example. We hope for Zig developers to employ DOD principles whenever appropriate, which is not going to be that rare in a low-level programming language like Zig. Andrew has a full talk about how the Zig compiler benefits tremendously from DOD: https://vimeo.com/649009599?embedded=true&source=video_title&owner=45836359 https://vimeo.com/649009599?embedded=true&source=video_title...
- kazinator 4y agoHere is Awk with C99 preprocessing: cppawk! Loop macro for parallel/nested iteration featuring a (user-extensible!) vocabulary of clauses: $ cppawk ' #include <cons.h> #include <iter.h> BEGIN { loop (list(iter0, item, list("alpha", "charlie", "bravo")), list(iter1, ltr, list("a", "b", "c")), range(i, 1, 3)) { print item, ltr, i } }' alpha a 1 charlie b 2 bravo c 3 This is a tiny shell script plus a collection of header files in a small directory structure. It requires an Awk such as GNU Awk, and the GNU C preprocessor. Preprocessed programs cam be captured, to run on systems that don't have the cppawk script or a preprocessor, and with less startup overhead. https://www.kylheku.com/cgit/cppawk/about/ https://www.kylheku.com/cgit/cppawk/about/
- codethief 4y agoWhile this solves one of the issues I've had with Zig, it doesn't seem very flexible. I would love to the same thing for (tuples of) variable-length arrays, arrays of different lengths etc. Now my implementation will still look completely different in slightly different situations. Yes, a flexible solution (a zip function) would probably need iterators but why would introducing them be such a big problem? (FWIW, I know that one can emulate looping over iterators with while() and optionals[0] but it feels a bit dirty.) More generally, my biggest gripe with Zig has been a lack of expressiveness: Things like deep equality checks between structs, arrays and optionals; looping over different kinds of containers; … should just work™. Sure, I understand that Zig doesn't want hidden control flow but OTOH explicit control flow everywhere often just gets in the way of readability and of implementing business logic. I usually follow the approach "Implement first, optimize later" but with Zig the implementation will look completely different depending on which optimization or data structure I choose, so I need to think about optimizations from the start if I don't want to rewrite everything later. I should mention, though, I'm very used to Python these days and haven't written C or C++ in ages, so maybe that sort of culture shock is somewhat expected. Anyway, I'm still excited about the language and my impression is that Andrew Kelley is very open to new suggestions and new ideas, so things will certainly still change in one way or another till v1.0. [0]: https://news.ycombinator.com/item?id=34958051 https://news.ycombinator.com/item?id=34958051
- AndyKelley 4y agoThere is no "just work" for deep equality. The standard library would have to make decisions on behalf of the application that it has no business making. I can tell you right now that while there will be many upcoming language changes, none of them will be comfy to Python programmers. Zig is very much an imperative language. Or perhaps think of it as a declarative DSL for outputting machine code.
- nemo1618 4y agoThis is a gripe I have about Go -- a very minor gripe, to be sure, but it's still there. If you want to iterate over two arrays/slices that have the same length, you have to choose between: for i := 0; i < n; i++ { fn(foo[i], bar[i]) } for i := range foo { fn(foo[i], bar[i]) } for i := range bar { fn(foo[i], bar[i]) } for i, x := range foo { fn(x, bar[i]) } for i, y := range bar { fn(foo[i], y) } But none of these are satisfactory; what I really want to write is: for _, (x, y) := range (foo, bar) { fn(x, y) }
- maxmcd 4y agoThis is pretty ugly and add the overhead of a function callback, but just for fun: func multiLoop[X, Y any](x []X, y []Y, cb func(i int, x X, y Y)) { if len(x) != len(y) { panic("invalid slice lengths") } for i := 0; i < len(x); i++ { cb(i, x[i], y[i]) } } func foo() { multiLoop([]int{1, 2, 3}, []string{"a", "b", "c"}, func(i int, x int, y string) { fmt.Println(i, x, y) }) }
- masklinn 4y agoFWIW this is often called `zipWith`, or sometimes just `map` (some `map` implementations can take a variable number of sequences to map over).
- noobermin 4y ago>In the multi-sequence for loop version it’s only necessary to test once at the beginning of the loop that the two arrays have equal size, instead of having 2 assertions run every loop iteration. So, I'm assuming zig generally cannot be used with multi-threaded code? Can the underlying arrays not be modified during the whole loop execution?
- int_19h 4y agoArrays can be modified, but their size is a part of their type, just like C. For slices, length is only known at runtime, but it's immutable once the slice is created, so there's no issue there, either.
- deleted 4y ago[deleted]
- masklinn 4y agoThat's broken in every language, except for the few which just don't allow doing it. So I'm not quite sure what the question is about.
- avgcorrection 4y agoSpecial-casing (same-length) zip and iteration+count might make sense for an imperative language which doesn’t want to go down the rabbit hole of implementing efficient, lazy iterators. It doesn’t make sense in a language where you want the flexibility of switching between (as in: compiling to) serial loops and paralell code, but it makes sense for a language which leans more towards what-you-see-is-what-you-get rather than sufficiently-smart-compiler.
- noobermin 4y agoTbh, there are limits to how much any language that does "wysiwyg" compilation that would have for loops. For example, any "for" loop can be a "while" loop in asm, the one optimization is you can use the index registers as long as the number of arrays is less than the number of index registers you have. If it is more, which the language does not constrain of course, you just go back to a loop with memory locations for pointers. But of course, in that case then, you must have a "smart compiler" that can decide that which case it is and thus compile to the right code. That said, this likely will be an esoteric case on most modern machines (like x86_64 has 16 regs that can be used for indexes) and I doubt people want to use this for like avr.
- Terretta 4y agocoming from total unawareness of zig: in the for (1..5) construct, these integer ranges consistently not including the upper limit element when lists do include the last element, seems surprising. i guess it's a range boundary (1 TO 5), not a list (1 THROUGH 5), but the other behavior feels like a list, so it feels like 5 should be in.
- throwawaymaths 4y agoIt's consistent with (some) other languages, for example iirc in ruby .. is exclusive of the last item and ... Includes the last item.
- cmoski 4y agoI completely agree with you on the madness of not including the upper limit. However, I don't see how the phrase "one to five" would not include five. "Rate this film on a scale of one to five" does not mean four is the highest rating. It translates to "increment from one, stopping before you get to five". Ridiculous.
- kzrdude 4y agoIt's not ridiculous, "1 to 5" translates into it starts with 1 and ends with 5, and both versions are ambiguous on the point of including the endpoint or not. In a programming context, it seems "clear" that it's ambiguous or down to convention.
- Wildgoose 4y agoDon't think of it as TO, think of it as increasing UNTIL. 1 increasing UNTIL 5. It is genuinely recommended (Djikstra onwards) to specify ranges as Start Value (inclusive) Until Final Value (exclusive).
- tialaramex 4y ago> Ranges can only exist as an argument to a for loop. This means that you can’t store them in variables I am confident this is a mistake. Every time you make a new kind of "thing" in your language somebody will want to do all the same stuff with it that they did with the other things, such as integers, ie in this case store a range in a variable. Ideally you'd just always be able to do that, see Lisp, but it can get very unwieldy, thus this is a reason to avoid making new kinds of thing so the issue doesn't arise. C++ chooses to actually do the heavy lifting here, which is why std::format (and its inspiration fmt::format) was such an enormous undertaking -- C++ can express the idea of a function which takes a variable number of arguments and yet all those arguments are independently type checked at compile time, not via compiler magic but just as a normal feature of the language. This is an enormous labour, and because they don't have any way to fix syntax issues the resulting problem accumulate forever in their language so I cannot recommend it as a course to other languages. It's like the Pyramids, do not build giant stone tombs for your leaders, this is a bad idea and your society should not copy it - however, the ancient Egyptians already did build giant stone tombs and they're pretty awesome to look at. Anyway, Rust chose to make its half-open range type std::ops::Range an actual type which you can store in a variable, pass to functions, modify etc. as well as using it in a for loop. Obviously don't copy Rust here exactly, for one thing Range should probably be IntoIterator, not an Iterator itself if they had it over, but you will wish this was an ordinary type in your language, so, just do it now. let a = 0..4; // The Range starting at zero and (non-inclusively) ending at four.
- masklinn 4y agoThe problem is that zig's designers apparently don't want to introduce an iterator abstraction, hence the frankenstein-ing of the for loop instead. Though in fairness getting an iterator abstraction to the same efficiency as a for loop requires pretty brutal optimisations, frankensteining your for loop, a lot less so.
- throwawaymaths 4y agoI don't know how ranges are implemented now (and I'm too lazy to check right now) but It's entirely possible zig's ranges could wind up as comptime-only values. Then you could pass them around, but only at comptime, which will achieve many of the things you expect. There's also nothing stopping you from creating an iterator interface in userland.
- tuukkah 4y ago> The new multi-sequence syntax allows you to loop over two or more arrays or slices at the same time In Haskell, this is called a parallel list comprehension: [x+y | x <- xs | y <- ys] In a normal list comprehension, you have a single pipe, in a parallel one you have as many pipes as how many lists you are zipping. https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/parallel_list_comprehensions.html https://downloads.haskell.org/ghc/latest/docs/users_guide/ex...
- masklinn 4y agoNo, your list comprehension is a product (it iterates ys for every x). The feature here is zip.
- tuukkah 4y agoI'll quote from the documentation link I referenced: > For example, the following zips together two lists: [ (x, y) | x <- xs | y <- ys ] That's precisely the difference between a normal list comprehension (one pipe) and a parallel list comprehension (multiple pipes). For clarity, here's your normal list comprehension (with one pipe) that produces all the combinations instead: [ (x, y) | x <- xs, y <- ys ] And here's the full example from the article converted to Haskell and producing the exact same output: {-# LANGUAGE ParallelListComp #-} import Control.Monad (mapM) elems = [ "water", "earth", "fire", "air" ] nats = [ "tribes", "kingdom", "nation", "nomads" ] main = mapM putStrLn [ show idx ++ " - " ++ e ++ " " ++ n | e <- elems | n <- nats | idx <- [0..] ] EDIT: I suppose an explicit zip with an anonymous function looks more idiomatic though: main = forM (zip3 elems nats [0..]) $ \(e, n, idx) -> putStrLn (show idx ++ " - " ++ e ++ " " ++ n) EDIT2: Best of both worlds with the list monad? main = mapM putStrLn $ do (e, n, idx) <- zip3 elems nats [0..] [ show idx ++ " - " ++ e ++ " " ++ n ]
- puffoflogic 4y agoSorry, but using new syntax to accomplish something other languages have as library code is not clever. When reading zig code you have to stop and think, "wait does this syntax mean zip or direct product?" But when expressed as a function called zip, the meaning is clear. (Obligatory reminder that zig devs think that sometimes running code inside `if (false)` is a minor bug of no consequence, and after all what are the real motives of anyone pointing it out, eh?)
- stephc_int13 4y agoI have absolutely no problem with the good old C-style for loop syntax. I think that a separate foreach or for-each loop, made for built-in or extended containers could be a nice addition. Not seeing much value there.
- andrewstuart 4y agoHang on, I was reading last night that Zig has no for loop? That you have to use while.... is this not correct?
- messe 4y agoIt has no “for (init; cmp; step)” type loop, and instead you had to use: var i: usize = 0; while (i < sz) : (i += 1) { ... } Meaning that the scope for i would leak. It did have a foreach-style for loop, as seen in the article though.
- kzrdude 4y agoLooks like they have the most important part in place, the increment before the next iteration.
- andrewstuart 4y agoOff topic, but I was weighing up trying Zig last night for a project. No doubt Zig has changed alot and is better than it was only a year or two ago. Is anyone here willing to say if they have experienced success and satisfaction using Zig? I'm wanting to do some C library interfacing.
- blameitonme 4y agoHey Im just a student and cant even think to build stuff of complexity most of the guys here make rn, but I made a json parser in zig and it was fun.
- cturtle 4y agoI’ve absolutely had satisfaction with my several personal projects written in Zig. And based on an imperfect measurement (GitHub stars) I have also had moderate success in making something useful. It’s a terminal fuzzy finder [0]. I also maintain a Zig Lua bindings package [1], and I’m working on a port of an old Macintosh game [2]. Zig is exactly what I want out of a language though, so take my opinion with a grain of salt :) [0]: https://github.com/natecraddock/zf https://github.com/natecraddock/zf [1]: https://github.com/natecraddock/ziglua https://github.com/natecraddock/ziglua [2]: https://github.com/natecraddock/open-reckless-drivin https://github.com/natecraddock/open-reckless-drivin
- andrewstuart 4y ago>> Zig is exactly what I want out of a language though What is exactly what you want from a language?
- cturtle 4y agoI value simplicity and control. Zig is a very consistent language in syntax and semantics, so there are a small number of features I need to be concerned with. My understanding is that once Zig reaches a stable 1.0 the language will not change. Although there is a lot of churn right now, I appreciate the idea of a language that is simple, and stays simple. The code is also very readable. I haven't found another language (yet) that I can just open up the standard library source code and understand just about everything. With no hidden control flow I can easily read a function and not have to question what a line of code does. Everything it does is right in front of me. I also love that Zig is trying to fix many of C's problems. Rather than a global libc allocator, each function that can allocate (by convention) accepts an allocator as an argument. In my projects this has been really great for understanding what code paths allocate, which has made it easy to package my fuzzy finder as an allocation-free Zig module and C library. Now, if I were working on a project with more critical safety requirements, I might consider a different language. But for most of my personal projects Zig is exactly what I need.
- conaclos 4y agoIs there other rationale behind this `for` and `while` syntax? Why not: for x in elms {} for x in elms, i in 0.. {} In my view, this seems simpler to read and understand. In particular, the iteration variable is close to the iterated structure. In the Zig proposal you have to think about the position of the iterated structure and the position of the iteration variable. for (elms, 0..) |x, i| {} ^^^ ^ Ok it is in second position I still don't get why `||`. Is this a lambda? Can I write something like: fn func(x: i8, i: i8) void {} for (elms, 0..) func In the same vein I do not understand the rationale behind the `while` syntax. Why `:`? while (condition) : (i += 1) {
- messe 4y agoBecause it’s consistent with the rest of Zig’s capture syntax: if (foo()) |result| { while (it.next()) |elem| { bar() catch |err| …
- throwawaymaths 4y agoIt is legitimately harder to parse if `in` is a special keyword. Zig's parser is defined in a context-free grammar and having implemented it, trust me the way it's done now is waaaaaay simpler.
- spoiler 4y agoThe richness of syntax in Zig has kinda put me off from learning it. At the risk of sounding like a total ass: when I was checking Zig out (albeit briefly), it reminded me of the "Awful Taste, Great Execution" meme. I remember loving the ideas, but groaning internally when I saw how they need to be used. If someone is interested I could skim docs to see if I can still find an example of what put me off. Things also seemed arbitrary and inconsistent sometimes, but that could've been a wrong impression since I didn't give it a proper shot overall. It's a very new language, so I'm sure a lot of it is still in flux anyways. Also, this was all about a year and a half ago, maybe a bit more. So, maybe things improved since then.
- halayli 4y ago``` for (monster_elem_types, monster_hps) |et, hp| { if (et == .fire) hp. +|= 1; // saturating addition } ``` Anyone knows what language Zig was inspired by(if any)? This doesn't look intuitive nor does it resemble any common syntax.
- edflsafoiewq 4y ago|x| for captures is from Ruby. Special ops are formed by adding another symbol onto an operator: wrapping ops are +% -% etc, saturating ops are +| -| etc. This pattern was perhaps inspired by OCaml, which uses +. -. etc for floating point ops. The | for saturating is because "| looks like a wall". The <op>= syntax is of course from C. p.* is pointer deference. It was probably chosen for parsing reasons. The * is C for dereference, the . prevents ambiguity with multiplication. It may have been inspired by the postfix p^ from Pascal. The .fire is just MyEnumType.fire with MyEnumType omitted; the type will be inferred.
- eggy 4y agoA quick syntax question: std.debug.print("{} ", .{n}); What is the '.' before '{n}' for? Thanks!
- cultureswitch 4y agoThis Data Oriented Design thing they mention later is basically just giving a name to an anti-pattern that is obvious to everyone since 1987.