如何重新使用已将值移出的盒子?
How can I reuse a box that I have moved the value out of?
我有一些不可复制的类型和一个使用并(可能)生成它的函数:
type Foo = Vec<u8>;
fn quux(_: Foo) -> Option<Foo> {
Some(Vec::new())
}
现在考虑一种在某种程度上在概念上与 Box
非常相似的类型:
struct NotBox<T> {
contents: T
}
我们可以编写一个函数,临时移出 NotBox
的内容并在 return 之前将内容放回原处:
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
}
}
我可以 return 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
})
}
是否可以解决这个问题?
在编译器中,框外移动是特殊情况。你可以从它们中移出一些东西,但你不能将一些东西移回去,因为移出的行为也会释放。您可以使用 std::ptr::write
、std::ptr::read
和 std::ptr::replace
做一些愚蠢的事情,但很难做到正确,因为 something valid 应该在Box
掉落时。我建议只接受分配,或者改用 Box<Option<Foo>>
。
We can write a function that temporarily moves out contents of the NotBox and puts something back in before returning it
那是因为您可以部分地从按值获取的结构中移出。它的行为就好像所有字段都是单独的变量一样。这是不可能的,但如果结构实现 Drop
,因为 drop
需要整个结构始终有效(以防出现恐慌)。
至于提供解决方法,您没有提供足够的信息——尤其是,为什么 baz
需要将 Box
作为参数而 quux
不能?哪些功能是您的,哪些是您无法更改的 API 的一部分? Foo
的真实类型是什么?大吗?
最好的解决方法是根本不使用 Box
。
所以,搬出 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 })
}
我有一些不可复制的类型和一个使用并(可能)生成它的函数:
type Foo = Vec<u8>;
fn quux(_: Foo) -> Option<Foo> {
Some(Vec::new())
}
现在考虑一种在某种程度上在概念上与 Box
非常相似的类型:
struct NotBox<T> {
contents: T
}
我们可以编写一个函数,临时移出 NotBox
的内容并在 return 之前将内容放回原处:
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
}
}
我可以 return 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
})
}
是否可以解决这个问题?
在编译器中,框外移动是特殊情况。你可以从它们中移出一些东西,但你不能将一些东西移回去,因为移出的行为也会释放。您可以使用 std::ptr::write
、std::ptr::read
和 std::ptr::replace
做一些愚蠢的事情,但很难做到正确,因为 something valid 应该在Box
掉落时。我建议只接受分配,或者改用 Box<Option<Foo>>
。
We can write a function that temporarily moves out contents of the NotBox and puts something back in before returning it
那是因为您可以部分地从按值获取的结构中移出。它的行为就好像所有字段都是单独的变量一样。这是不可能的,但如果结构实现 Drop
,因为 drop
需要整个结构始终有效(以防出现恐慌)。
至于提供解决方法,您没有提供足够的信息——尤其是,为什么 baz
需要将 Box
作为参数而 quux
不能?哪些功能是您的,哪些是您无法更改的 API 的一部分? Foo
的真实类型是什么?大吗?
最好的解决方法是根本不使用 Box
。
所以,搬出 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 })
}