如何一次将值从数组中移出一个?
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<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));
【讨论】:
和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);
如果数组非常大,那可能是个问题。但是,如果数组非常大,您一开始就不应该在堆栈中创建它!
【讨论】:
“但是,如果阵列很大,此解决方案无法很好地扩展。”您是指性能方面的扩展,还是只是写出来很烦人?以上是关于如何一次将值从数组中移出一个?的主要内容,如果未能解决你的问题,请参考以下文章