17

I can destructure a vector of tuples by taking a slice of a vector and references to the items within the tuple:

let items = vec![("Peter".to_string(), 180)];

if let [(ref name, ref age)] = items.as_slice() {
    println!("{} scored {}", name, age);
};

How can I destructure the vector directly, moving the items out of the tuple. Something like this:

let items = vec![("Peter".to_string(), 180)];

if let [(name, age)] = items {
    println!("{} scored {}", name, age);
};

Compiling the above results in the error:

error[E0529]: expected an array or slice, found `std::vec::Vec<(std::string::String, {integer})>`
 --> src/main.rs:4:12
  |
4 |     if let [(name, age)] = items {
  |            ^^^^^^^^^^^^^ pattern cannot match with input type `std::vec::Vec<(std::string::String, {integer})>`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Peter Horne
  • 6,472
  • 7
  • 39
  • 50
  • related question: http://stackoverflow.com/questions/28851989/how-can-i-pattern-match-a-vect-inside-an-enum-field-without-nesting-matches – oli_obk Mar 28 '15 at 11:38
  • for future visitors: there is a proposal for deref patterns coming to rust. This will let you deref Box, Arc, Vec, and other types – Albert May 23 '22 at 11:38

2 Answers2

16

You are asking two disjoint questions at once:

  1. How can I move out of a vector?
  2. How can I destructure an item?

The second is easy:

let item = ("Peter".to_string(), 180);
let (name, score) = item;

You don't need the if let syntax because there no way for this pattern-matching to fail. Of course, you can't use item after destructuring it because you've transferred ownership from item to name and score.

The first question is harder, and gets to a core part of Rust. If you transfer ownership out of a vector, then what state is the vector in? In C, you would have some undefined chunk of memory sitting in the vector, waiting to blow apart your program. Say you called free on that string, then what happens when you use the thing in the vector that pointed to the same string?

There are a few ways to solve it...

The vector continues to own the items

let items = vec![("Peter".to_string(), 180)];

if let Some((name, score)) = items.first() {
    println!("{} scored {}", name, score);
}

Here, we grab a reference to the first item and then references to the name and score. Since the vector may not have any items, it returns an Option, so we do use if let. The compiler will not let us use these items any longer than the vector lives.

Transfer one element's ownership from the vector

let mut items = vec![("Peter".to_string(), 180)];

let (name, score) = items.remove(0); // Potential panic!
println!("{} scored {}", name, score);

Here, we remove the first item from the array. The vector no longer owns it, and we can do whatever we want with it. We destructure it immediately. items, name and score will all have independent lifetimes.

Transfer all element ownership from the vector

let items = vec![("Peter".to_string(), 180)];

for (name, score) in items {
    println!("{} scored {}", name, score);
}

Here, we consume the vector, so it is no longer available to use after the for loop. Ownership of name and score is transferred to the variables in the loop binding.

Clone the item

let items = vec![("Peter".to_string(), 180)];

let (name, score) = items[0].clone(); // Potential panic!
println!("{} scored {}", name, score);

Here, we make new versions of the items in the vector. We own the new items, and the vector owns the original ones.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks! It's a shame that moving items out of a vector can't be supported by the compiler transferring ownership in the same way as it does when destructuring the `item` tuple. – Peter Horne Mar 28 '15 at 16:08
  • 1
    @PeterHorne I'm not sure what you mean. If `foo = vec[0]` transferred ownership out of the vector and into the variable `foo`, what would you want `vec[0]` to mean *after* the ownership was transferred? – Shepmaster Mar 28 '15 at 16:21
  • Same as if you try to access `items` after `(a, b) = items` – an error (use of partially moved value) – Peter Horne Mar 28 '15 at 16:58
  • 2
    @PeterHorne: You are vastly over-estimating the Rust type system of the moment though. Partial Moves are only available for structures for which the `Drop` trait is not implemented (see http://is.gd/tRTWDR => *cannot move out of type `Person`, which defines the `Drop` trait*), because then the compiler cannot know what the `Drop` trait will rely on. – Matthieu M. Mar 29 '15 at 12:42
  • If you do need to use `.remove(0)`, changing the data structure to a `VecDeque` and using `.pop_front()` will give you better performance, and no panic. – Penz Jan 22 '22 at 21:27
3

You can't do this, the definition of Vec in std is

pub struct Vec<T> {
    ptr: Unique<T>,
    len: usize,
    cap: usize,
}

so you can't match it directly, only:

match xs {
    Vec { ptr: x, .. } => {...}
} 

but

error: field `ptr` of struct `collections::vec::Vec` is private
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
uwu
  • 1,590
  • 1
  • 11
  • 21