colinfang
colinfang

Reputation: 21727

How to destructure a Vec into variables that take ownership?

I have a struct

struct Foo {
    foo1: String,
    foo2: String,
    foo3: String,
    foo4: String,
    // ...
}

I would like to create an instance of Foo from a vector.

let x = vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];
match x.as_slice() {
    &[ref a, ref b, ref c, ref d] => {
        let foo = Foo {
            foo1: a.to_string(),
            foo2: b.to_string(),
            foo3: c.to_string(),
            foo4: d.to_string(),
        };

    },
    _ => unreachable!(),
}

Do I have to copy the strings? Is there any better way to destructure the vector into a, b, c, d as well as transferring the ownership?

Actually, I don't mind x is completely destroyed after the destructuring. So I hope there is a pattern match for vectors apart from slices as well. For now it seems we can only destructure slices.

Upvotes: 8

Views: 3936

Answers (4)

David Gallay
David Gallay

Reputation: 47

The question is old but for anyone who is looking for a better answer: There is no way to do this as wanted.

The solution is to split the pattern matching and destructuring in 2 steps:

let x = vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];
match x.len() {
    4 => {
        let [a, b, c, d] = x.try_into().unwrap();
        //let foo = Foo {...};
    },
    // For at least 4 elements, we can use range and .take(n)
    4.. => {
        let [a, b, c, d] = x.take(4).try_into().unwrap();
        //let foo = Foo {...};
    },
    _ => unreachable!(),
}

if you want some more complex pattern matching, you may need to duplicate the destructuring, once for the check and another for the actual destructuring.

Upvotes: 0

Filipe Rodrigues
Filipe Rodrigues

Reputation: 2177

You can use the TryFrom implementation of arrays to do this:

let x = vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];
let [a, b, c, d] = <[String; 4]>::try_from(x).expect("Vec didn't have 4 elements");

Upvotes: 1

Shepmaster
Shepmaster

Reputation: 430673

Do I have to copy the strings?

Not if you are willing to give up destructuring. I'm a big fan of itertools:

use itertools::Itertools; // 0.8.2

fn main() {
    let x = vec![
        "a".to_string(),
        "b".to_string(),
        "c".to_string(),
        "d".to_string(),
    ];

    if let Some((foo1, foo2, foo3, foo4)) = x.into_iter().tuples().next() {
        let foo = Foo {
            foo1,
            foo2,
            foo3,
            foo4,
        };
    }
}

This transfers ownership of the vector (and thus the members) to an iterator, then the tuples adapter chunks up the values into a tuple. We take the first one of those and construct the value.

You could also use drain if you didn't want to give up ownership of the entire vector:

if let Some((foo1, foo2, foo3, foo4)) = x.drain(..4).tuples().next() {

Is there any better way to destructure the vector into a, b, c, d as well as transferring the ownership?

No, there is no mechanism to take ownership of a part of a Vec without creating another Vec (or another type that has the same limits) except for an iterator.

Upvotes: 7

Manishearth
Manishearth

Reputation: 16188

Destructuring slices isn't stable, and you can't move out of a slice because it's just a borrow — if you moved out, what would the Vec's destructor do?

Mutating the vector is the way to go here:

let mut x = vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];
let foo = Foo {
    foo4: x.pop().unwrap(),
    foo3: x.pop().unwrap(),
    foo2: x.pop().unwrap(),
    foo1: x.pop().unwrap(),
};

println!("{:?}", foo);

playground

Upvotes: 1

Related Questions