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

izkcnapc  于 2022-11-30  发布在  其他
关注(0)|答案(4)|浏览(203)

我拥有一个大小为3的数组的所有权,我想在它上面进行迭代,随着我的操作将元素移出。基本上,我想为一个固定大小的数组实现IntoIterator
既然数组在标准库中没有实现这个特性(我知道为什么),有没有一个变通的方法来获得想要的效果?我的对象既不是Copy,也不是Clone。我可以从数组中创建一个Vec,然后迭代到Vec中,但我甚至不知道如何做到这一点。
(For信息,我想实现一个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
wrrgggsh

wrrgggsh1#

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) {}

生 rust 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将数组转换为mem::MaybeUninit数组,然后使用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);
    }
}
r6l8ljro

r6l8ljro2#

您可以使用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));
    }
}
mftmpeh8

mftmpeh83#

使用non-lexical lifetimes feature(从Rust 1.31.0开始可用)和 * 固定长度的切片模式 *(从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);
    }
}

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

gcuhipw9

gcuhipw94#

自生 rust 1.65起稳定

这是LendingIterator的开发用例:

extern crate lending_iterator;
use ::lending_iterator::prelude::*;

// 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 array: [Foo; 3] = [Foo, Foo, Foo];

    let mut iter = array.into_lending_iter();
    while let Some(a) = iter.next() {
        bar(a);
    }
}

相关问题