6 ms·
Depends 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
by laserbeam 4y ago
Depends 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
- defen 4y agoCan that zip more than two iterators? And does it perform a bounds check on each call to `a.next()` and `b.next()`?
- brundolf 4y agoIt stops when one of the two iterators ends It can't zip more than two per se, but you could zip the result of the first zip into a third and get ((item1, item2), item3). You could then map these if you wanted, to flatten them into a single tuple .map(|((item1, item2), item3)| (item1, item2, item3)) Of course there's a trade-off here between ergonomics and generality
- masklinn 4y ago> It can't zip more than two per se, but you could zip the result of the first zip into a third and get ((item1, item2), item3). You could then map these if you wanted, to flatten them into a single tuple .map(|((item1, item2), item3)| (item1, item2, item3)) FWIW that's more or less what `itertools::izip!` does for you, it just chains `zip`s then "splats" them using a `map`.
- defen 4y ago> It stops when one of the two iterators ends Right; my question is, suppose you're iterating over two slice iterators - won't each call to `a.next()` and `b.next()` have to check whether that sub-iterator is done? One of the benefits of the Zig approach is that you can iterate over an arbitrary number of slices and do one check before entering the loop, followed by the compiler emitting unchecked index access in the loop. So it basically compiles down to the equivalent of a C `for` loop.
- masklinn 4y agoRust's zip has a specialisation for iterators with a trusted length. Such as slice iterators. `zip` yields exactly the same assembly as a loop over the index range with an unsafe item access: https://godbolt.org/z/7ebfxbhxc https://godbolt.org/z/7ebfxbhxc
- laserbeam 4y agoYou're assuming that an iterator gets created and returned by zip. In zig the for loop syntax doesn't need the concept of an iterator. All you need is variables in a capture. That's what I mean by straightforward. You'd have to argue about the memory layout for an iterator, that you call functions on it etc. Lots of small decisions. In zig those things would usually be part of the standard library, not language features. If you want to call a next() function on a thing in zig, you want to be explicit. That's what the language means by "no hidden control flow". You'd use a while loop if you wanted to iterate on a hashmap for example. In some sense, this is less elegant, but it's usually more obvious what the compiler would end up doing in any bit of code.
- gpanders 4y ago>Rust also hates hidden allocations Does it? Rust seems happy to allocate silently all the time. let x = String::new(“hi”); let y = vec![]; Do either of these allocate? As the writer or reader of this code, how do I know if either of these statements result in a heap allocation, or if the data is strictly on the stack? Zig’s requirement of explicitly passing around an Allocator type removes any ambiguity completely.
- steveklabnik 4y ago(You’re forgetting a “new” in the string example)
- gpanders 4y agoThanks, I fixed it :)
- deleted 4y ago[deleted]
- brundolf 4y agoSure, any arbitrary function (or macro) logic can allocate. It's more a philosophy, not something that's language-enforced[0] in Rust- if you're creating a mutable, variable-size data structure like a String or a Vec or a HashMap you're not going to be very surprised that it allocates at some point (though technically zero-length Vecs don't allocate on construction, they wait until an item is added) But closures don't require allocation, iterators don't require allocation, async doesn't require allocation. Copy semantics also don't allow allocation- implicit copies can only happen for data structures that are bitwise-copyable, which is enforced by the compiler. For copy-with-allocation you have to implement the Clone trait, and then invoke it explicitly with the .clone() method But the original context was a question of philosophy, so I was only speaking to Rust's overall philosophy [0] Technically I think if you're using no_std you won't have access to any standard constructs that allocate (which obviously will prevent their use at compile-time), though I believe you're still allowed to eg. call out to foreign functions manually that would allocate. And of course, this still isn't as granular as Zig's allocation-control.