How do I move values out of an array?

情到浓时终转凉″ 提交于 2019-12-17 20:08:03

问题


I have ownership of an array of size 3 and I would like to iterate on it, moving the elements out as I go. Basically, I would like to have IntoIterator implemented for a fixed-sized array.

Since arrays don't implement this trait in the standard library (I understand why), is there a workaround to get the desired effect? My objects are not Copy nor Clone. I'd be okay creating a Vec from the array and then iterating into the Vec, but I'm not even sure how to do that.

(For information, I'd like to fulfill an array of Complete)

Here is a simple example of the situation (with a naive iter() attempt):

// No-copy, No-clone struct
#[derive(Debug)]
struct Foo;

// A method that needs an owned Foo
fn bar(foo: Foo) {
    println!("{:?}", foo);
}

fn main() {
    let v: [Foo; 3] = [Foo, Foo, Foo];

    for a in v.iter() {
        bar(*a);
    }
}

playground

Gives

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:14:13
   |
14 |         bar(*a);
   |             ^^ cannot move out of borrowed content

回答1:


The core thing you would need is some way of getting the value out of the array without moving it. Two such solutions are:

  1. Use mem::replace and mem::uninitialized to replace each value in the array with garbage, getting the original back:

    let one = unsafe { mem::replace(&mut v[0], mem::uninitialized()) };
    bar(one);
    
  2. Use ptr::read to leave the value in the array but get an owned value back:

    let two = unsafe { ptr::read(&v[0]) };
    bar(two);
    

It's just a matter of doing one of these a few times in a loop and you are good to go.

There's just one tiny problem: you see those unsafe? You guessed it; this is totally, horribly broken in the wider case:

  • We are leaving that array full of arbitrary bits that will still be treated like a Foo. In this case, nothing special happens when that array goes out of scope as there's no special Drop implementation for the type Foo, but if there was, it would be accessing invalid memory. Bad news!
  • You could use mem::forget to ignore the array and prevent it from being dropped, but...
  • If a panic happens in the middle of moving the values out (such as somewhere within the bar function), the array will be in a partially-uninitialized state. This is another (subtle) path where the Drop implementation can be called, so now we have to know which values the array still owns and which have been moved out. We are responsible for freeing the values we still own and not the others.
  • Nothing prevents us from accidentally accessing the newly-invalidated values in the array ourselves.

The right solution is to track how many of the values in the array are valid / invalid. When the array is dropped, you can drop the remaining valid items and ignore the invalid ones. You also have to avoid the automatic destructor from running. It'd also be really nice if we could make this work for arrays of different sizes...

Which is where arrayvec comes in. It doesn't have the exact same implementation (because it's smarter), but it does have the same semantics:

extern crate arrayvec;

use arrayvec::ArrayVec;

#[derive(Debug)]
struct Foo;

fn bar(foo: Foo) {
    println!("{:?}", foo)
}

fn main() {
    let v = ArrayVec::from([Foo, Foo, Foo]);

    for f in v {
        bar(f);
    }
}

In nightly Rust, you can use std::array::IntoIter to get a by-value array iterator:

#![feature(array_value_iter)]

use std::array::IntoIter;

fn main() {
    let v: [Foo; 3] = [Foo, Foo, Foo];

    for a in IntoIter::new(v) {
        bar(a);
    }
}



回答2:


You may use array of Option<Foo> instead array of Foo. It has some memory penalty of course. Function take() replaces value in array with None.

#[derive(Debug)]
struct Foo;

// A method that needs an owned Foo
fn bar(foo: Foo) { println!("{:?}", foo); }

fn main() {
    let mut v  = [Some(Foo),Some(Foo),Some(Foo)];

    for a in &mut v {
        a.take().map(|x| bar(x));
    }
}



回答3:


Using the non-lexical lifetimes feature (only in nightly) and a fixed-length slice pattern you can move out of an array:

#![feature(nll)]

#[derive(Debug)]
struct Foo;

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

fn main() {
    let v: [Foo; 3] = [Foo, Foo, Foo];
    let [a, b, c] = v;

    bar(a);
    bar(b);
    bar(c);
}

However, this solution does not scale well if the array is big.

An alternative, if you don't mind the extra allocation, is to box the array and convert it into a Vec:

fn main() {
    let v: [Foo; 3] = [Foo, Foo, Foo];
    let v = Vec::from(Box::new(v) as Box<[_]>);

    for a in v {
        bar(a);
    }
}

If the array is very big, that may be an issue. But then, if the array is very big, you should not be creating it in the stack in the first place!



来源:https://stackoverflow.com/questions/34406251/how-do-i-move-values-out-of-an-array

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!