我有一些不可复制的类型和一个消耗和(可能)生成它的函数:
type Foo = Vec<u8>;
fn quux(_: Foo) -> Option<Foo> {
Some(Vec::new())
}
现在考虑一种在某种程度上与Box
非常相似的类型:
struct NotBox<T> {
contents: T
}
我们可以编写一个临时移出NotBox
内容的函数,并在返回之前重新输入内容:
fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> {
let foo = notbox.contents; // now `notbox` is "empty"
match quux(foo) {
Some(new_foo) => {
notbox.contents = new_foo; // we put something back in
Some(notbox)
}
None => None
}
}
我想编写一个与Box
es兼容的类似函数,但编译器不喜欢它:
fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
let foo = *abox; // now `abox` is "empty"
match quux(foo) {
Some(new_foo) => {
*abox = new_foo; // error: use of moved value: `abox`
Some(abox)
}
None => None
}
}
我可以返回Some(Box::new(new_foo))
但是执行不必要的分配 - 我已经拥有了一些内存!有可能避免这种情况吗?
我还想摆脱match
语句,但编译器再次对它不满意(即使对于NotBox
版本):
fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> {
let foo = notbox.contents;
quux(foo).map(|new_foo| {
notbox.contents = new_foo; // error: capture of partially moved value: `notbox`
notbox
})
}
可以解决这个问题吗?
答案 0 :(得分:9)
所以,离开Box
是一个特例......现在是什么?
std::mem
模块提供了许多安全功能来移动值,而不会将漏洞(!)放入Rust的内存安全性中。这里感兴趣的是swap
和replace
:
pub fn replace<T>(dest: &mut T, src: T) -> T
我们可以这样使用:
fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
let foo = std::mem::replace(&mut *abox, Foo::default());
match quux(foo) {
Some(new_foo) => {
*abox = new_foo;
Some(abox)
}
None => None
}
}
它也有助于map
案例,因为它没有借用Box
:
fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
let foo = std::mem::replace(&mut *abox, Foo::default());
quux(foo).map(|new_foo| { *abox = new_foo; abox })
}
答案 1 :(得分:4)
移出盒子在编译器中是特殊的。你可以从中移出一些东西,但是你无法移回一些东西,因为搬出的行为也会解除分配。您可以使用std::ptr::write
,std::ptr::read
和std::ptr::replace
做一些愚蠢的事情,但很难做到正确,因为某些有效应该在{{1}内当它被删除。我建议只接受分配,或改为转换为Box
。
答案 2 :(得分:0)
我们可以编写一个暂时移出NotBox内容的函数,并在返回之前重新放入内容
那是因为你可以部分地从按价值的结构中移出。它表现得好像所有字段都是单独的变量。如果结构实现Drop
,那是不可能的,因为drop
需要整个结构有效,总是(如果发生恐慌)。
至于提供解决方法,您还没有提供足够的信息 - 尤其是,为什么baz
需要Box
作为参数以及为什么quux
无法解决?您可以使用哪些功能以及哪些功能是您无法更改的API的一部分? Foo
的真实类型是什么?它太大了吗?
最好的解决方法是根本不使用Box
。