我有一个为某些容器实现移动语义的最小代码示例:
use std::mem;
impl<'a, T: 'a + ?Sized> Drop for Cointainer<'a, T> {
fn drop(&mut self) {}
}
struct Cointainer<'a, T: 'a + ?Sized> {
item: &'a /* mut */ T,
}
impl<'a, T> Cointainer<'a, T> {
fn mv(self) -> Cointainer<'a, T> {
let new = Cointainer { item: /* &mut */ self.item };
mem::forget(self);
new
}
}
fn main() {}
编译和运行没有任何问题。
我意识到我需要对
Cointainer::item
引用的值进行变异,所以我已经创建了引用mut
。当我这样做时,我得到:error[E0505]: cannot move out of `self` because it is borrowed
--> src/main.rs:14:21
|
13 | let new = Cointainer { item: /* &mut */ self.item };
| --------- borrow of `*self.item` occurs here
14 | mem::forget(self);
| ^^^^ move out of `self` occurs here
我需要创建一个新容器并在那里转移
item
的所有权,然后删除旧容器。这个例子是人为的。实际的“move”操作执行一些其他操作,并不一定返回相同的容器类型。
最佳答案
rules of references state:
在任何给定的时间,您可以拥有但不能同时拥有:
一个可变引用。
任意数量的不可变引用。
具有不可变引用的代码可以工作,因为这些引用可以自由复制。带有可变引用的代码失败,因为据编译器所知,您需要有两个并发可变引用:在new
中保存的引用,然后mem::forget
中可能也需要它。
作为人类,我们认识到mem::forget
无法进入我们结构的内脏。这就是unsafe
代码的作用:当编译器不能保证我们拥有的代码是真正安全的时候。
一个小的unsafe
块和一些对原始指针的强制转换可以解决这个问题。与任何不安全的代码一样,它应该有一个很大的注释块来解释为什么编译器不理解它,以及为什么它是真正安全的。
impl<'a, T: 'a + ?Sized> Drop for Cointainer<'a, T> {
fn drop(&mut self) {
println!("dropping 1: {:p}", self.item)
}
}
struct Cointainer<'a, T: 'a + ?Sized> {
item: &'a mut T,
}
impl<'a, T> Cointainer<'a, T> {
fn into_inner(self) -> &'a mut T {
// I copied this code from Stack Overflow but didn't read
// the warning about explaining why it's safe. Sure hope
// this doesn't cause any bugs!
unsafe {
let x = self.item as *mut _;
std::mem::forget(self);
&mut *x
}
}
fn mv(self) -> Cointainer2<'a, T> {
let item = self.into_inner();
Cointainer2 { item }
}
}
struct Cointainer2<'a, T: 'a + ?Sized> {
item: &'a mut T,
}
impl<'a, T: 'a + ?Sized> Drop for Cointainer2<'a, T> {
fn drop(&mut self) {
println!("dropping 2: {:p}", self.item)
}
}
fn main() {
let mut a = String::new();
let c1 = Cointainer { item: &mut a };
let c2 = c1.mv();
}
关于rust - 如何在具有可变字段的结构上实现 move 语义?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48485203/