Should `Vec` have a `try_remove(&mut self, usize) -> Option<T>` function

This seems like a common enough request that it has probably been discussed elsewhere, but the closest I could find was this Reddit thread regarding changing Vec::remove to return an Option, which is obviously a very breaking change.

My suggestion is a new function with the rough implementation:

impl<T> Vec<T> {
  fn try_remove(&mut self, index: usize) -> Option<T> {
    if self.len() < index {
      Some(self.remove(index))
    } else {
      None
    }
  }
}

I imagine a similar function could be added for try_swap_remove.

Do people think this API would be useful? Is it "too similar to .remove()"? Personally I found it odd that such a function doesn't exist, but is that frustration shared with other people? Is there some reason I'm not seeing that makes such an API problematic?

I was planning to put up a PR, and get feedback there, but thought best to create a Github issue first, which brought me here.

3 Likes

I suppose you mean if self.len() > index.

Note that you can shorten this to

(index < self.len()).then(|| self.remove(index))

if you want to have try_remove-like functionality in your code right now.

Does libstd have any try_ methods yet that return Option? I think there would be a question whether try_ should be reserved only for Result methods (e.g. Result<T, VecIndexOutOfBoundsError>)

2 Likes

AFAICT, no.

 ~/forks/rust/library > master > rg -U "pub fn try.*\(([^{]|\n)*?\) -> Option"
std/src/sys/sgx/waitqueue/spin_mutex.rs
48:    pub fn try_lock(&self) -> Option<SpinMutexGuard<'_, T>> {

std/src/sys_common/remutex.rs
101:    pub fn try_lock(self: Pin<&Self>) -> Option<ReentrantMutexGuard<'_, T>> {

core/src/num/dec2flt/number.rs
52:    pub fn try_fast_path<F: RawFloat>(&self) -> Option<F> {

those all seem to be public-in-private.

It could be done for every single thing that takes an index.

It's not obvious to me that adding all those things is useful, though. Especially for remove, you have to have come up with the index from somewhere. So how often does it really come up that you're trying to remove something where you don't know that it's in-range already?

It's easy to check the index is in-range yourself, in a place where it really doesn't already know, so adding more methods for it doesn't seem worth it to me.

8 Likes

So how often does it really come up that you're trying to remove something where you don't know that it's in-range already?

Ah I see what you mean. The case that motivated this for me was "popping" items from the front of the stack, so my example use case would be vec.try_remove(0). Admittedly, that's probably the only way to call it without immediately unwrapping the Option you get back.

Maybe what I'm after instead would be something more like remove_first(&mut self) -> Option<T>? That also avoids the question of "can an Option-returning function be called try_foo"

At that point, though, wouldn't you want https://doc.rust-lang.org/std/collections/struct.VecDeque.html#method.pop_front instead? Not having a perfect method for something discouraged is often a good thing.

(And for 0 specifically, if !v.is_empty() { v.remove(0); } doesn't seem so bad.)

4 Likes

If you need to regularly remove the first item of a Vec, you might be using the wrong data structure, since every remove is an O(n) operation.

Unless you use swap_remove, but in that case, the order of the Vec is quickly messed up, and if order doesn't matter you might as well pop the last element to begin with.

To efficiently use remove and push in an alternating manner, use VecDeque; to remove a bunch of first items in a row, turning it into an iterator might make sense.

13 Likes

Yeah, sounds like I'm probably using the wrong data structure. Thanks for the insight :slight_smile:

1 Like

It's interesting to realize how I've never had a need for this method, despite frequently using get for its Option-ness.

I think the reason here is: the most common situation where you want any method that takes an index, is the situation where there are stable indices – e.g. you construct an array once, and then use array indices as positions you're moving around between, without adding or removing any elements from the array in the meantime. The moment you call remove, any indices after the removed element lose their meaning.

3 Likes