6

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.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
colinfang
  • 20,909
  • 19
  • 90
  • 173
  • 1
    [Compilable example](https://play.rust-lang.org/?gist=88c575973531ddf9f41d143ef54e3e22&version=nightly&backtrace=0) for experimentation. – user4815162342 Apr 27 '17 at 12:31
  • See also [Destructuring a vector (without taking a slice)](http://stackoverflow.com/q/29316582/155423) and [How can I unpack (destructure) elements from a vector?](http://stackoverflow.com/q/32324645/155423). – Shepmaster Apr 27 '17 at 13:34

3 Answers3

6

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
1

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

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Manishearth
  • 14,882
  • 8
  • 59
  • 76
  • 2
    Could `Vec::drain()` be combined with slice destructuring to provide the move+destructure semantics? – user4815162342 Apr 27 '17 at 13:18
  • @user4815162342 you cannot move out of a slice because the data structure itself has no way to track that there's a "hole" where it was moved out of. Since `drain` provides an iterator, you could use it with my answer, if you wanted to keep the `Vec` around. – Shepmaster Apr 27 '17 at 13:29
  • @Shepmaster I was responding to the part of the answer that says that you can't move out of a slice. Since `drain()` is specifically made to move elements out of a collection, I was curious about the possibility of combining `drain()` and slice destructuring, maybe with an intermediate type sitting between them. Your answer is of course fine, but it doesn't use slice destructuring. – user4815162342 Apr 27 '17 at 14:34
  • @user4815162342 gotcha. My point is that *slice destructuring* works on *slices* and you can't move out of slices as they always represent a borrowed value, regardless of which route you'd take to get there. It sounds like you'd need some other destructuring technique ("vec destructuring"?). – Shepmaster Apr 27 '17 at 14:37
  • @Shepmaster Yeah, I guess I hoped that *slice destructuring* was in fact a misnomer and that it can work on more than just slices. :) The name being correct means that you can only ever use `ref x` inside `&[...]`, at least for non-`Copy` types, right? – user4815162342 Apr 27 '17 at 17:39
  • @user4815162342 that certainly sounds believable. If it's always `ref`, that might fit with the ergonomics initiative [around `match` and references](https://github.com/rust-lang/rust-roadmap/issues/24). – Shepmaster Apr 27 '17 at 17:41
1

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");
Filipe Rodrigues
  • 1,843
  • 2
  • 12
  • 21
  • I arrived at almost the same code while trying to solve a similar issue, except there is a slice in the box, not an array. This yields an error of moving out of slice, even though the pattern destructures the entire slice. Is there an aspect of destructuring the slice that forbids such behaviour? i.e. ownership of elements being transferred before it is certain that the pattern is not going to refuse – Tesik Mar 08 '23 at 19:29
  • @Tesik I believe since slices have an unknown length, you just aren't allowed to move anything out of them, in general, not even just a single element. Maybe in the future this will be changed, but currently that's the way it is. – Filipe Rodrigues Mar 08 '23 at 23:15
  • One more remark about this solution: Since this only works with arrays and not with slices, the box can be omitted entirely. `let [a, b, c, d] = <[String; 4]>::try_from(x).expect(":(");` – Tesik Mar 09 '23 at 11:33
  • @Tesik You're right. I, for some reason, thought that only `Box<[T; N]>` had `TryFrom` implementations for some reason. – Filipe Rodrigues Mar 09 '23 at 16:00