Why doesn't into_iter_mut exist?

It dawned on me how useful it would be to hypothetically be able to both mutate and map a Vec<Vec<T>> into an owned type Vec<Vec<U>> in a single iteration and without cloning.

Example: first sort and dedup Vec<T> before mapping into Vec<U>

I could only think of a way to do this involving two iterations.

  1. To mutate, call: iter_mut() then mutate each Vec<T> (because of borrow, don't map)
  2. To map, call: into_iter() (to avoid borrow) and then map into a newly owned Vec<U>

Is there a way to do it with only 1 iteration without cloning? If such a way does exist, why wasn't it called into_iter_mut? And if not, why can't it exist feasibly?

You can mutate an owned value though

let a: Vec<Vec<usize>> = vec![vec![1, 3, 2], vec![5, 4, 6]];

let b: Vec<Vec<String>> = a
    .into_iter()
    .map(|mut v| {
        v.sort();
        v.into_iter().map(|i| i.to_string()).collect::<Vec<_>>()
    })
    .collect::<Vec<_>>();

println!("{b:?}")

If that's not what you're trying to do, some code doing what you want "the hard way" would probably help clarify things

5 Likes

There are 3 basic ways to “pass a value” in Rust, each of which has their own method (by convention or by trait):

  1. By reference: &T (conventional iterator method .iter())
  2. By mutable reference: &mut T (conventional iterator method .iter_mut())
  3. By move: T (IntoIterator method .into_iter())

In the type system, “move with mutation” is not a different thing from “move”, so it doesn't have separate methods. A move means the recipient of the moved value gets to do whatever with it, including mutating it.

2 Likes

It's worth adding a note that std provides some special implementation(s) which allows this .into_iter().map(_).collect() pattern for Vecs to remain in-place and not require reallocating into a new Vec allocation.

Not that any cloning would be involved without this specialization, but it does reduce the amount of moves/copying necessary.

5 Likes

Roughly speaking, does the optimization only apply when map doesn't change the size of the elements?

At least some of the specializations require the size and alignment of the elements have to be the same, yes

I believe this is the relevant conditional

1 Like

Yes, and it would almost certainly be unsound if not. Note that Rust requires passing the same size/align layout when deallocating as when the allocation was made, so recollecting to a different required alignment must create a fresh allocation and mustn't reuse the old one, as that would be unsound. (Even if the alignment requirement is lower!)

Don't rely on this happening for correctness — it's not guaranteed — but it does happen most of the time it's possible as a performance optimization, so it's generally not necessary to do manually.

2 Likes

Seems like we can probably count on the simplest case, where the map return type and param type are the same, being optimized.

Edit: But of course we might as well mutate the elements in that case, so never mind... :slight_smile: