Reputation: 359
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
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
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
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
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