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

Posted

技术标签:

【中文标题】如何一次将值从数组中移出一个?【英文标题】:How do I move values out of an array one at a time? 【发布时间】:2016-03-28 03:38:08 【问题描述】:

我拥有一个大小为 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

【问题讨论】:

【参考方案1】:

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 将数组转换为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);
    

【讨论】:

谢谢。因此,单独使用Array 并没有安全的方法,但使用ArrayVec 可能是它与Vec 之间的折衷方案。 感谢您添加ptr::read 解决方案。它使意图比replace + uninitialized 或我使用的替代方案transmute_copy 更清晰。 您应该提到mem::forget应该在将未初始化的内存写入数组之后使用。 @torkleyy 好吧,这两个unsafe 解决方案都被故意破坏了。 mem::forget 是正确的,但前提是 all 值已成功移出。这就是关于 arrayvec 的全部意义所在:正确处理细节真的很难。 是的,我知道,这正是我正在做的事情。【参考方案2】:

您可以使用Option&lt;Foo&gt; 数组代替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));
    

【讨论】:

Option::take is implemented 和mem::replace ^_^。 Option 具有始终可以创建的值,并且适合作为“虚拟”或“无操作”值 (None)。 @shepmaster 不同之处在于您的项目中缺少不安全的代码。 @MahmoudAl-Qudsi 我不确定我是否关注你。 所有 解决方案(这个和我的答案中的两个)使用不安全的代码,唯一的区别是它在哪里。这个在标准库中。我的箱子里有内联的一个和一个。【参考方案3】:

使用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);
    

如果数组非常大,那可能是个问题。但是,如果数组非常大,您一开始就不应该在堆栈中创建它!

【讨论】:

“但是,如果阵列很大,此解决方案无法很好地扩展。”您是指性能方面的扩展,还是只是写出来很烦人?

以上是关于如何一次将值从数组中移出一个?的主要内容,如果未能解决你的问题,请参考以下文章

如何一次将一个项目添加(推送)到一个数组(循环遍历数组)

如何将值从 listObjects 推送到数组

如何使用 ajax 传递 from.serialize() 和数组值?

如何将值从数据库中的laravel存储到数组中?

将值从一个数组追加到另一个未知维度的数组

将值从一个函数传递到另一个C ++