Idiomatic way to move a `Vec` into 1 of its items by an index (dropping the rest of the item except one at index)?

Given a variable vec of type Vec<Item> where Item is not Copy,

Simply writing vec[index] would cause error:

return vec[index]; // error: cannot move out of `vec`

So I must wrote this convoluted code:

vec.truncate(index);
return vec.pop().unwrap();

This looks inefficient and stupid.

Is there a better way? (I don't need the rest of vec after I have vec[index])

swap_remove might be just the thing. It'll efficiently swap in the last element in place of the one you're removing, saving a lot of shuffling for a Vec you're about to drop anyway.

5 Likes

Is there no method that doesn't do anything extra (such as swaping elements)?

Something like:

impl Vec<T> {
    fn into_element(self, index: usize) -> T {
        // self is moved here and would be dropped, except self[index] which would return
    }
}

I'm almost certain the answer is no, not currently. I remember this coming up in an earlier discussion.

Alternatively, if the type implements Default you could std::mem::take out the one you want.

But leaving a "hole" in the Vec at any point isn't going to work out in safe Rust.

Probably any "better" way would involve not producing a whole materialized Vec if you only need one item? What's the surrounding context here?

The surrounding context is that I'm validating a JSON array and I need to return 1 invalid element as part of the error message (the Err path of the Result).

Your suggestion would probably also do extra work in the general case: Two loops to drop all the other elements, instead of one.

1 Like

You could try doing validation as part of the parse, eg with #[serde(deserialize_with = "path")]

That would simply call the derived Desrerialize then do your validation.


One issue here is you don't get a simple option for a strongly typed error, since the deserializer (eg serde_json) picks that.

Swapping is really fast in Rust, it's literally just a few memcpy so I won't mind that.

In principle, this should have no unnecessary moves:

fn keep_one<T>(v: Vec<T>, index: usize) -> Option<T> {
    v.into_iter().nth(index)
}

As @quinedot suggested it might, this results in two loops (one to drop the items before index and one for the items after)[1] — but that's merely duplicate machine code, not duplicate work at run time.

However, the difference between this and swap_remove() will be tiny unless size_of::<T>() is particularly large.


  1. if T has a destructor; if it doesn't, no loops at all are needed â†Šī¸Ž

10 Likes

Note that all of these solutions also have different effects of the order of the other drops, but that's a very niche concern.

1 Like

If there's no custom Drop, human intuition suggests that v.into_iter().nth(index) would iterate until index instead of accessing v[index] directly, resulting in O(n) complexity. Can we rely on the optimizer to turn it into O(1)?

The vec IntoIter overrides the advance_by function to optimize this case std source

6 Likes