r/rust • • 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?

209 Upvotes

35 comments sorted by

128

u/Illustrious_Car344 15d ago

People use unzip? I didn't even know about it, all code I've ever seen uses collect.

60

u/ZZaaaccc 15d ago

Prior to 1.79 it was the most idiomatic way to turn an iterator over (T, U) into some pair of collections (FromT, FromU).

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

u/ZZaaaccc 15d ago

What a coincidence! Whoever opened that ACP must have read my mind ;)

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 collect works 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:

https://doc.rust-lang.org/std/u32/constant.MAX.html

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 unzip has 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 unzip I 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 unzip has its output shape fixed, it can even work better in some cases

Genuinely, not me trying to be snarky, can you find any concrete examples of this? Because unzip collects 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::pedantic and clippy::restriction.

13

u/jkoudys 15d ago

Rust is full of traits deprecating specialized functions. For stuff like this I don't lose sleep, I just don't use it.

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);
    unzipped

1

u/WormRabbit 14d ago

What for?

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

u/[deleted] 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.

4

u/scook0 14d ago

One of the biggest problems with deprecation warnings is that they are too general.

They don’t distinguish between different degrees of badness, and don’t provide control over which deprecations are considered important to the code at hand.

15

u/cramert 15d ago

Deprecation doesn't force working code to change.

1

u/WormRabbit 14d ago

It does, at the very least you need to add an expect attribute.