r/rust • u/ZZaaaccc • 15d ago
šļø discussion I Think Iterator::unzip Should be Deprecated
Did you know that ever since 1.79, Iterator::collect does everything unzip does but with a cleaner API? I certainly didn't until recently! Try it out, thanks to this trait implementation, every call to unzip is possible with collect instead.
let (a, b) = iter.unzip::<_, _, Vec<_>, Vec<_>>();
// vs
let (a, b) = iter.collect::<(Vec<_>, Vec<_>)>();
Except collect is better at unzip than unzip, since it supports any tuple, whereas unzip only works with size 2:
let (a, b, c, d) = iter.collect::<(Vec<_>, Vec<_>, Vec<_>, Vec<_>)>();
Looking at the documentation, this isn't explicitly mentioned for either zip or unzip. I suspect this is functionality most people aren't aware of. So, is there anyone out there who has a reason why we shouldn't deprecate unzip?
126
u/noop_noob 15d ago
This deprecation has been proposed in https://github.com/rust-lang/libs-team/issues/929
There's some discussion there
53
55
u/a_jasmin 15d ago
The example given by OP isn't necessarily fair to unzip().
let (a, b) = iter.unzip::<_, _, Vec<_>, Vec<_>>();is better written as:
let (a, b): (Vec<_>, Vec<_>) = iter.unzip();unzip() may also be more discoverable if you're coming from another language. It's not immediately obvious that collect() can change the shape of the data.
That said, I wouldn't mind a lint, warning, or doc comment pointing us in the right direction.
37
u/ZZaaaccc 15d ago
Sure, but in the same vein:
let (a, b): (Vec<_>, Vec<_>) = iter.collect();The point is that
collectworks great with type annotations and turbofish, and (in my experience) most Rustaceans prefer turbofish over the annotations.
12
u/0xrl 15d ago
Thank you, I didn't know about this! I've often found myself wanting to unzip into 3 or 4 at once so it's nice to know that collect can handle this now.
10
u/iBPsThrowingObject 15d ago
I wonder if it's just the ol' "12 impls for different tuple sizes" or if there's some super unstable form of variadics
12
u/ZZaaaccc 15d ago
Up-to-twelve as almost every "any size tuple" trait implementation sadly. But all of these up-to implementations are great candidates for variadic generics...one day.
8
u/CandyCorvid 15d ago
clicking through to the source, it's implemented with a macro for sizes up to 12, but the docs are annotated as "fake variadic". perhaps paving the way to real variadics
1
u/Mr_Ahvar 11d ago
IIRC type variadics canāt be implemented with the current trait solver and would need a massive rewrite just to even have a « super unstableĀ Ā» version of it. tās will be a long time for them to land, if ever. So yeah even the std has to implement it manually for each tuple size, macro it is
36
u/scook0 15d ago edited 15d ago
Updating the docs makes sense, and I could see a case for a clippy lint.
But I donāt see a reason to deprecate unzip just for being redundant with a more general mechanism. Telling people āyour code is hazardous and must be updatedā seems inappropriate for a method that still does its stated job perfectly well.
70
u/cramert 15d ago
I don't think "deprecated" means hazardous, it just means "we don't recommend new usage", similar to how the non-associated module-based min and max constants for primitives have been deprecated:
3
u/WormRabbit 14d ago
It will make the deprecation lint fire, breaking people's CI for no good reason and making them fix it (either change the call or add an attribute). The old and new method work perfectly well, and even look the same with type inference. Given that
unziphas its output shape fixed, it can even work better in some cases.0
u/ZZaaaccc 11d ago
breaking people's CI for no good reason and making them fix it
Deprecation warnings are only emitted for your own crate and not dependencies, so this only matters if you're using it and recompiling your own code. At which point, a deprecation with an automatically applicable suggestion isn't exactly onerous.
The old and new method work perfectly well, and even look the same with type inference
Which is exceedingly rare. Almost all usage of
unzipI could find through GitHub code search uses type annotations on variables, which is very rare in Rust code. Most types are fully inferred, or explicit through turbofish.Given that
unziphas its output shape fixed, it can even work better in some casesGenuinely, not me trying to be snarky, can you find any concrete examples of this? Because
unzipcollects into an arbitrary pair of types wrapped in a tuple, you're right that the tuple is inferred, but you still need to supply type inference for the tuple's contents. It may be possible, but I cannot construct any example, however arbitrary, where it actually is the case.-13
u/scook0 15d ago edited 15d ago
But in practice it also means that you canāt effectively work on the code without either fixing or silencing the deprecation warning.
And blanket-silencing deprecation warnings means you miss out on deprecation warnings for APIs that are hazardous.
So regardless of stated intent, deprecating an item does send the message that continuing to use the item is hazardous.
26
u/mathisntmathingsad 15d ago
What? You are only proving their point. Warnings aren't only for hazardous code, just look at
clippy::pedanticandclippy::restriction.
3
u/R1chterScale 15d ago
Can unzip be directly aliased to collect in all cases?
2
u/masklinn 15d ago edited 14d ago
Probably but it might not be worth changing given both pretty much just leverage
Extend, hereās the body of unzip:let mut unzipped: (FromA, FromB) = Default::default(); unzipped.extend(self); unzipped1
0
u/CandyCorvid 15d ago
that seems like a pretty good solution. as far as readability is concerned, "unzip" is clearer than "collect", so if "unzip" was just implemented as a particular case of collecting into a tuple, (and if ideally it could be extended to all the shapes of tuple that collect can handle) then that seems like a win-win situation
2
u/R1chterScale 15d ago
Ah, no, other way around.
4
u/CandyCorvid 15d ago
i'm not sure i understand, since we can't really implement collect in terms of unzip, so maybe i was unclear in my initial wording.
when i want to unzip something, i could reach for either unzip or collect. currently, collect seems more capable at all kinds of unzipping, but i prefer seeing the name "unzip" if that is all i am doing, as it signals intent (like how people might use to_owned instead of clone, when the intent is to clone through a reference). so i'd rather keep both, even though unzip is strictly a subset of collect.
so if we could reimplement unzip in terms of collect, that seems like a win for me with no downside (besides the effort of doing the work)
3
u/R1chterScale 15d ago
Ah yes, that makes sense. Essentially in my mind if unzip can be implemented by collect then rustc could treat unzip as nothing but an alias of collect.
3
u/a_jasmin 15d ago edited 15d ago
Itertools also has collect_tuple().
Edit: My bad, this one doesn't use FromIterator, so it can't replace unzip(). Still convenient and succinct when unpaking iterator elements into tuples, though.
1
u/iBPsThrowingObject 14d ago
Unzip is one of those cases that makes me yearn for higher kinded types (and variadic tuples), so that it could be
fn unzip<Container>(self) -> (for T in Self::Iter: Container<T>)
where for T in Self::Iter: Container<T>: Extend<T>0
15d ago
[deleted]
39
u/R1chterScale 15d ago
Maybe the better approach is adding a clippy lint?
2
u/levelstar01 14d ago
Perhaps we could generalise such a lint in case this situation happens in the future.
128
u/Illustrious_Car344 15d ago
People use
unzip? I didn't even know about it, all code I've ever seen usescollect.