所以我遇到了这个代码片段,展示了如何在Rust中创建"不可移动"类型 - 因为编译器将对象视为在其整个生命周期中借用的对象,所以阻止了移动.
use std::cell::Cell;
use std::marker;
struct Unmovable<'a> {
lock: Cell<marker::ContravariantLifetime<'a>>,
marker: marker::NoCopy
}
impl<'a> Unmovable<'a> {
fn new() -> Unmovable<'a> {
Unmovable {
lock: Cell::new(marker::ContravariantLifetime),
marker: marker::NoCopy
}
}
fn lock(&'a self) {
self.lock.set(marker::ContravariantLifetime);
}
fn new_in(self_: &'a mut Option<Unmovable<'a>>) {
*self_ = Some(Unmovable::new());
self_.as_ref().unwrap().lock();
}
}
fn main(){
let x = Unmovable::new();
x.lock();
// error: cannot move out of `x` because it is borrowed
// let z = x;
let mut y = None;
Unmovable::new_in(&mut y);
// error: cannot move out of `y` because it is borrowed
// let z = y;
assert_eq!(std::mem::size_of::<Unmovable>(), 0)
}
Run Code Online (Sandbox Code Playgroud)
我还不明白这是如何工作的.我的猜测是借用指针参数的生命周期被强制匹配锁定字段的生命周期.奇怪的是,如果符合以下条件,此代码将以相同的方式继续工作:
ContravariantLifetime<'a>了CovariantLifetime<'a>,或者改为InvariantLifetime<'a>.lock方法的主体.但是,如果我删除了Cell,只是lock: marker::ContravariantLifetime<'a>直接使用,如下:
use std::marker;
struct Unmovable<'a> {
lock: marker::ContravariantLifetime<'a>,
marker: marker::NoCopy
}
impl<'a> Unmovable<'a> {
fn new() -> Unmovable<'a> {
Unmovable {
lock: marker::ContravariantLifetime,
marker: marker::NoCopy
}
}
fn lock(&'a self) {
}
fn new_in(self_: &'a mut Option<Unmovable<'a>>) {
*self_ = Some(Unmovable::new());
self_.as_ref().unwrap().lock();
}
}
fn main(){
let x = Unmovable::new();
x.lock();
// does not error?
let z = x;
let mut y = None;
Unmovable::new_in(&mut y);
// does not error?
let z = y;
assert_eq!(std::mem::size_of::<Unmovable>(), 0)
}
Run Code Online (Sandbox Code Playgroud)
然后允许"Unmoveable"对象移动.那为什么会这样?
真正的答案是对生命周期变异性的适度复杂的考虑,以及需要整理的代码中的一些误导性方面。
\n\n对于下面的代码,\'a是任意生命周期,\'small是小于 的任意生命周期\'a(这可以用约束 来表示\'a: \'small),并\'static用作大于 的生命周期的最常见示例\'a。
以下是考虑中应遵循的事实和步骤:
\n\n通常,寿命是逆变的;&\'a T相对于 是逆变的\'a(在没有T<\'a>任何变异标记的情况下),这意味着如果您有 a &\'a T,则可以用比 更长的生命周期替换它\xe2\x80\x99 \'a,例如,您可以在这样的地方存储 a&\'static T并对待就好像它是一个&\'a T(您\xe2\x80\x99可以缩短生命周期)。
在某些地方,生命周期可以是不变的;最常见的例子是&\'a mut Twhich 对于 而言是不变的\'a,这意味着如果你有 a &\'a mut T,你不能在其中存储 a &\'small mut T(借用没有\xe2\x80\x99t 存在足够长的时间),但你也不能&\'static mut T在其中存储 a ,因为这会给存储的引用带来麻烦,因为它会忘记它实际上存在的时间更长,因此您最终可能会同时创建多个可变引用。
ACell包含一个UnsafeCell; \xe2\x80\x99t 如此明显的是,它UnsafeCell是神奇的,被连接到编译器进行特殊处理,作为名为 \xe2\x80\x9cunsafe\xe2\x80\x9d 的语言项。重要的是,由于与关于 的不变性类似的原因,UnsafeCell<T>是关于 的不变性。T&\'a mut T\'a
因此,实际上将表现得与 相同。Cell<any lifetime variancy marker>Cell<InvariantLifetime<\'a>>
此外,你\xe2\x80\x99实际上不需要Cell再使用;你可以只使用InvariantLifetime<\'a>.
Cell返回到移除包装和 a的示例ContravariantLifetime(实际上相当于仅定义struct Unmovable<\'a>;,因为逆变是默认值,没有Copy实现):为什么它允许移动值?\xe2\x80\xa6 我必须承认,我\xe2\x80\x99t还没有理解这个特殊情况,并且希望有人帮助我自己理解为什么它\xe2\x80\x99s被允许。似乎回到前面,协变将允许锁短暂存在,但逆变和不变性不会\xe2\x80\x99t,但实际上,似乎只有不变性才能执行所需的功能。
无论如何,这里\xe2\x80\x99就是最终结果。Cell<ContravariantLifetime<\'a>>更改为InvariantLifetime<\'a>and that\xe2\x80\x99s 是唯一的功能更改,使该lock方法能够按需要运行,并借用具有不变生命周期的借用。(另一种解决方案是使用locktake &\'a mut self,因为正如已经讨论的那样,可变引用是不变的;然而,这是较差的,因为它需要不必要的可变性。)
另外需要提及的是:lock和new_in方法的内容完全是多余的。函数体永远不会改变编译器的静态行为;只有签名才重要。生命周期参数被标记为不变这一事实\'a是关键点。所以整个\xe2\x80\x9c构造一个Unmovable对象并调用lock它的\xe2\x80\x9d部分new_in是完全多余的。同样,设置单元格的内容lock也是浪费时间。(请注意,起作用的是\'ain的不变性,而不是它是可变引用这一事实。)Unmovable<\'a>new_in
use std::marker;\n\nstruct Unmovable<\'a> {\n lock: marker::InvariantLifetime<\'a>,\n}\n\nimpl<\'a> Unmovable<\'a> {\n fn new() -> Unmovable<\'a> {\n Unmovable { \n lock: marker::InvariantLifetime,\n }\n }\n\n fn lock(&\'a self) { }\n\n fn new_in(_: &\'a mut Option<Unmovable<\'a>>) { }\n}\n\nfn main() {\n let x = Unmovable::new();\n x.lock();\n\n // This is an error, as desired:\n let z = x;\n\n let mut y = None;\n Unmovable::new_in(&mut y);\n\n // Yay, this is an error too!\n let z = y;\n}\nRun Code Online (Sandbox Code Playgroud)\n