如何一次将一个值从数组中移出?

How do I move values out of an array one at a time?

我拥有一个大小为 3 的数组的所有权,我想对其进行迭代,同时将元素移出。基本上,我想为固定大小的数组实现 IntoIterator

由于数组没有在标准库中实现这个特性(我明白为什么),是否有解决方法来获得预期的效果?我的对象不是 Copy 也不是 Clone。我可以从数组创建一个 Vec,然后迭代到 Vec,但我什至不知道该怎么做。

(有关信息,我想完成 Complete 的数组)

这里有一个简单的例子(天真iter()尝试):

// 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

给予

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

Rust 2021(可从 Rust 1.56 获得)

您可以使用 for 循环迭代数组:

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

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

struct Foo;
fn bar(_: Foo) {}

生锈 1.51

您可以使用 std::array::IntoIter 获取按值数组迭代器:

use std::array::IntoIter;

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

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

struct Foo;
fn bar(_: Foo) {}

以前的 Rust 版本

您需要的核心是在不移动数组的情况下从数组中获取值。

这可以使用 mem::transmute to convert the array to an array of mem::MaybeUninit, then using ptr::read 将值保留在数组中但取回拥有的值来完成:

let one = unsafe {
    let v = mem::transmute::<_, [MaybeUninit<Foo>; 3]>(v);
    ptr::read(&v[0]).assume_init()
};
bar(one);

只需循环执行几次即可。

只有一个小问题:你看到了吗unsafe?你猜到了;在更广泛的情况下,这完全、可怕地被打破了:

  • MaybeUninit 掉落时什么都不做;这可能会导致内存泄漏。
  • 如果在移出值的过程中发生恐慌(例如 bar 函数中的某处),数组将处于部分未初始化状态。这是另一个可以删除 MaybeUninit 的(微妙的)路径,所以现在我们必须知道数组仍然拥有哪些值,哪些值已被移出。我们有责任释放我们仍然拥有的价值,而不是其他价值。
  • 没有什么能阻止我们自己意外访问数组中新近失效的值。

正确的解决方案是跟踪数组中有多少值有效/无效。删除数组时,您可以删除剩余的有效项并忽略无效项。如果我们能让它适用于不同大小的数组,那就太好了...

这就是 arrayvec 的用武之地。它没有 完全相同的实现(因为它更智能),但它确实具有相同的语义:

use arrayvec::ArrayVec; // 0.5.2

#[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);
    }
}

您可以使用 Option<Foo> 数组代替 Foo 数组。它当然有一些内存损失。函数 take() 将数组中的值替换为 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));
    }
}

使用non-lexical lifetimes feature (available since Rust 1.31.0) and a fixed-length slice pattern (available since Rust 1.26.0)你可以移出一个数组:

#[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);
}

但是,如果数组很大,则此解决方案无法很好地扩展。

如果您不介意额外分配,另一种方法是将数组装箱并将其转换为 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);
    }
}

如果数组很大,那可能是个问题。但是,如果数组很大,你不应该首先在堆栈中创建它!