Method .take() on Vec


#1

This is a nice pattern that takes the idea of Option::take and extends it to Vec.


/// Replace the value with an "empty" state and return the original value
trait Take {
    fn take(&mut self) -> Self;
}

impl<T> Take for Vec<T> {
    fn take(&mut self) -> Self {
        ::std::mem::replace(self, Vec::new())
    }
}

This is nice because it allows writing code that replaces a vector with a map of it (playground)

Edited to actually use ownership semantics, where this matters: A vec of Strings for example.

fn update_everything(data: &mut Vec<String>) {
    *data = data.take().into_iter().filter_map(|x| {
        if x.len() > 5 {
            None
        } else {
            Some(x + " ")
        }
    }).collect();
}

This method makes the ownership situation in the code easier to understand. I guess replace(&mut foo, Vec::new()) can look like magic to newcomers.

It needs an element with ownership semantics of course, I guess the f32 example missed the mark by a bit.


#2

isn’t that simply

    let d = data.drain(..).filter_map(|x| {
        if x <= 10. {
            Some(x * x)
        } else { None }
    }).collect();
    *data = d;

or using retain_mut once that RFC gets through


#3

I usually use Vec::split_off for this.

let mut vec = vec![1,2,3];
let vec2 = vec.split_off(0);
assert!(vec.is_empty());
assert_eq!(vec2, [1, 2, 3]);

It has the same interface as the take you propose.


#4

retain_mut is a good alternative, if you want in-place mutation instead of mapping to a new vec.

What I was trying to show is that .take() is nice when you want to consume the vec and then create a new one.


#5

This is a trait for C++ style non-destructive move semantics! It would be nice to have such trait in libstd.
There are plenty of types that can implement it.


#6

What about:

trait Yoink {
    /**
    Yoinks the current value out from under the owner's feet, replacing it with
    the default value... hopefully before anyone notices.
    */
    fn yoink(&mut self) -> Self;
}

impl<T> Yoink for T where T: Default {
    fn yoink(&mut self) -> Self {
        ::std::mem::replace(self, Self::default())
    }
}

Then you can yoink all sorts of things.


#7

Can’t we just implement this as a method on the Default trait?


#8

Not without being core.


#9

You say that like it’s a problem. std::mem is a re-export of core::mem so I don’t see why it couldn’t be in libcore.


#10

It’s sad, but your name is better (HashSet is already using .take() for a different purpose). The nice thing about a less spray-y version is that Vec’s implementation is totally cheap and appropriate for the kind of replace-map-assign transformation, while most other collections are not that nice.


#11

take is already quite overloaded:

  • io::Read::take: Could lead to confusion since Vec<u8> implements Write and thus may already be used as a kind of stream.
  • iter::Iterator::take: Vec is also very closely related to iterators.

At least those two are the same basic concept.


#12

Yes. The feature is more important than the name.


#13

For name-bikeshedding, I suggest Default::replace_default(&mut self) – implemented with mem::replace as suggested, but of course that can be overridden if any type wants for some reason.


#14

#15

While I agree, I think a more functional approach like in-place mapping or retain_mut as you mentioned would be clearer and easier to use. I know at least that I would rather use a higher order function that did everything in place than think about move/copy semantics. Speaking of which, is this code equivalent to your example or is it less efficient?

fn update_everything(data: &mut Vec<String>) {
    *data = data.into_iter().filter_map(|x| {
        if x.len() > 5 {
            None
        } else {
            Some(x.clone() + " ")
        }
    }).collect();
}

#16

It’s less efficient since .into_iter() on a &mut Vec gives you the same as iter_mut(), so x is a &mut String. Since the string is taken by reference, a full copy of each string is forced, and that’s the overhead.