rust - 这个错误是由于编译器对 RefCell 的特殊了解吗?

标签 rust borrow-checker

fn works<'a>(foo: &Option<&'a mut String>, s: &'a mut String) {}
fn error<'a>(foo: &RefCell<Option<&'a mut String>>, s: &'a mut String) {}

let mut s = "hi".to_string();

let foo = None;
works(&foo, &mut s);

// with this, it errors
// let bar = RefCell::new(None);
// error(&bar, &mut s);

s.len();

如果我在注释中输入两行,会出现以下错误:

error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
  --> <anon>:16:5
   |
14 |     error(&bar, &mut s);
   |                      - mutable borrow occurs here
15 |     
16 |     s.len();
   |     ^ immutable borrow occurs here
17 | }
   | - mutable borrow ends here

works()errors() 的签名看起来非常相似。但显然编译器知道您可以使用 RefCell 作弊,因为借用检查器的行为不同。

我什至可以在我自己的另一种类型中“隐藏”RefCell,但编译器仍然总是做正确的事情(如果可以使用 RefCell 会出错).编译器如何知道所有这些东西以及它是如何工作的?编译器是否将类型标记为“内部可变性容器”或类似的东西?

最佳答案

RefCell<T>包含 UnsafeCell<T> 这是一个特殊的lang item .是UnsafeCell导致错误。你可以检查:

fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {}

...

let bar = UnsafeCell::new(None);
error(&bar, &mut s);

但错误不是由于编译器识别出 UnsafeCell 引入了内部可变性,而是由于 UnsafeCellinvariant在 T. 事实上,我们可以使用 PhantomData 重现错误:

struct Contravariant<T>(PhantomData<fn(T)>);

fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {}

...

let bar = Contravariant(PhantomData);
error(bar, &mut s);

甚至是生命周期内逆变或不变的任何东西 'a :

fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}

let bar = None;
error(bar, &mut s);

您不能隐藏 RefCell 的原因是因为方差是通过结构的字段派生的。一旦你使用了 RefCell<T>在某处,无论多深,编译器都会计算出 T是不变的。


现在让我们看看编译器是如何判断E0502错误的。首先,重要的是要记住编译器必须在这里选择两个特定的生命周期:表达式类型中的生命周期&mut s。 ( 'a ) 和 bar 类型的生命周期(我们称之为 'x )。两者都受限:前世'a必须短于 s 的范围,否则我们最终会得到比原始字符串生命周期更长的引用。 'x必须大于 bar 的范围,否则我们可以通过 bar 访问悬空指针(如果一个类型有一个生命周期参数,编译器假定该类型可以访问具有该生命周期的值)。

有了这两个基本的限制,编译器会经过以下步骤:

  1. 类型barContravariant<&'x i32> .
  2. error函数接受 Contravariant<&'a i32> 的任何子类型, 其中'a是那个 &mut s 的生命周期表达。
  3. 因此bar应该是 Contravariant<&'a i32> 的子类型
  4. Contravariant<T>T 上是逆变的,即如果 U <: T , 然后 Contravariant<T> <: Contravariant<U> .
  5. 所以当&'x i32时子类型关系可以满足是 &'a i32父类(super class)型 .
  6. 因此'x应该'a ,即 'a应该 'x .

同样,对于不变类型,派生关系是'a == 'x ,对于协变,'x超过生命周期'a .

现在,这里的问题是 bar类型 中的生命周期一直存在到范围结束(根据上述限制):

    let bar = Contravariant(PhantomData);   // <--- 'x starts here -----+
    error(bar,                              //                          |
          &mut s);                          // <- 'a starts here ---+   |
    s.len();                                //                      |   |
                                            // <--- 'x ends here¹ --+---+
                                            //                      |
                                            // <--- 'a ends here² --+
}

// ¹ when `bar` goes out of scope
// ² 'a has to outlive 'x

在逆变和不变的情况下,'a比(或等于)'x表示语句 s.len()必须包含在范围内,导致 borrowck 错误。

只有在协变的情况下,我们才能使范围为 'a短于 'x , 允许临时对象 &mut ss.len() 之前删除被调用(意思是:在 s.len()s 不再被认为是借用的):

    let bar = Covariant(PhantomData);       // <--- 'x starts here -----+
                                            //                          |
    error(bar,                              //                          |
          &mut s);                          // <- 'a starts here --+    |
                                            //                     |    |
                                            // <- 'a ends here ----+    |
    s.len();                                //                          |
}                                           // <--- 'x ends here -------+

关于rust - 这个错误是由于编译器对 RefCell 的特殊了解吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43210387/

相关文章:

rust - 有什么方法可以将 Box<Box<Foo + Send>> 转换为 Box<Box<Foo>> 吗?

rust - 我可以使用像传值方法这样的可变引用方法吗?

rust - 当类型为 `Copy` 时,方法是否应该 move (`self` ) 或借用 (`&self` ) 类型?

rust - 无法移出尝试反序列化 hyper::Request.body() 的借用内容

rust - 无法移出共享引用后面的 `*h`

multithreading - Rust 中为线程或函数创建超时的正确方法是什么?

rust - 为什么在 hyper 中匹配请求路径后没有任何反应?

rust - rust 中的链式 if 语句?

loops - E0597试图在向量上循环

rust - 对元组的可变引用作为输入参数