vandenheuvel
vandenheuvel

Reputation: 359

How can I (slice) pattern match on an owned Vec with non-Copy elements?

My goal is to move elements out of an owned Vec.

fn f<F>(x: Vec<F>) -> F {
    match x.as_slice() {
        &[a, b] => a,
        _ => panic!(),
    }
}

If F is copy, that is no problem as one can simply copy out of the slice. When F is not, slice patterns seem a no-go, as the slice is read only.

Is there such a thing as an "owned slice", or pattern matching on a Vec, to move elements out of x?

Edit: I now see that this code has the more general problem. The function

fn f<T>(x: Vec<T>) -> T {
    x[0]
}

leaves "a hole in a Vec", even though it is dropped right after. This is not allowed. This post and this discussion describe that problem.

That leads to the updated question: How can a Vec<T> be properly consumed to do pattern matching?

Upvotes: 4

Views: 3239

Answers (4)

bluenote10
bluenote10

Reputation: 26550

I came across a nice pattern in a recent reddit post that's applicable in case you expect your vector to have exactly the length you are matching against.

The idea to exploit that Vec implements TryInto<[T; N]> via TryFrom (since Rust version 1.48). This can be combined with the let else syntax to pattern match on the try result:

let Ok([a, b]) = TryInto::<[_; 2]>::try_into(x) else { panic!("Vector has wrong length")};

If the vector can have more elements, and you want to discard (drop) them anyway, you could truncate it beforehand.

Upvotes: 5

phimuemue
phimuemue

Reputation: 35983

If you insist on pattern matching, you could do this:

fn f<F>(x: Vec<F>) -> F {
    let mut it = x.into_iter();
    match (it.next(), it.next(), it.next()) {
        (Some(x0), Some(_x1), None) => x0,
        _ => panic!(),
    }
}

However, if you just want to retrieve the first element of a 2-element vector (panicking in other cases), I guess I'd rather go with this:

fn f<F>(x: Vec<F>) -> F {
    assert_eq!(x.len(), 2);
    x.into_iter().next().unwrap()
}

Upvotes: 3

Peter Hall
Peter Hall

Reputation: 58735

You can't use pattern matching with slice patterns in this scenario.

As you have correctly mentioned in your question edits, moving a value out of a Vec leaves it with uninitialized memory. This could then cause Undefined Behaviour when the Vec is subsequently dropped, because its Drop implementation needs to free the heap memory, and possibly drop each element.

There is currently no way to express that your type parameter F does not have a Drop implementation or that it is safe for it to be coerced from uninitialized memory.

You pretty much have to forget the idea of using a slice pattern and write it more explicitly:

fn f<F>(mut x: Vec<F>) -> F {
    x.drain(..).next().unwrap()
}

If you are dead set on pattern matching, you can use Itertools::tuples() to match on tuples instead:

use itertools::Itertools; // 0.9.0

fn f<F>(mut x: Vec<F>) -> F {
    match x.drain(..).tuples().next() {
        Some((a, _)) => a,
        None => panic!()
    }
}

Upvotes: 2

nngg
nngg

Reputation: 208

One way to achieve consuming a single element of a vector is to swap the last element with the element you want to consume, and then pop the last element

fn f<F>(mut x: Vec<F>) -> F {
    match x.as_slice() {
        [_a, _b] => {
            x.swap(0, 1);
            x.pop().unwrap() // returns a
        },
        _ => panic!(),
    }
}

The code uses an unwrap which isn't elegant.

Upvotes: 1

Related Questions