为什么从引用切换到 RefCell 会导致借用检查器失败?

问题描述 投票:0回答:1

Rust (1.78.0) 成功编译以下代码:

struct Foo<'a>(&'a mut usize);
impl<'a> Foo<'a> {
    pub fn func(&self, _s: &'a mut String) -> &'a str {
        "hello world"
    }
}

fn test_func(arg: &Foo) {
    let mut buf = String::new();
    let _s = arg.func(&mut buf);
}

现在我更改

Foo
的定义以将引用保留在
RefCell
内:

struct Foo<'a>(std::cell::RefCell<&'a mut usize>);
impl<'a> Foo<'a> {
    pub fn func(&self, _s: &'a mut String) -> &'a str {
        "hello world"
    }
}

fn test_func(arg: &Foo) {
    let mut buf = String::new();
    let _s = arg.func(&mut buf);
}

现在失败了:

error[E0597]: `buf` does not live long enough
  --> src/main.rs:12:23
   |
10 | fn test_func(arg: &Foo) {
   |              --- has type `&Foo<'1>`
11 |     let mut buf = String::new();
   |         ------- binding `buf` declared here
12 |     let _s = arg.func(&mut buf);
   |              ---------^^^^^^^^-
   |              |        |
   |              |        borrowed value does not live long enough
   |              argument requires that `buf` is borrowed for `'1`
13 | }
   | - `buf` dropped here while still borrowed

尽管没有修改函数签名,但为什么此更改会导致借用检查器失败?感谢您的帮助!

rust borrow-checker
1个回答
1
投票

你的例子并不是真正等价的,因为在第一种情况下

Foo::func
不能改变
&mut usize
后面的
&self
,而在第二种情况下它可以。

特别是,如果在第一种情况下

Foo::func
收到
&mut self
而不是
&self
,你会遇到同样的错误(playground):

struct Foo<'a>(&'a mut usize);
impl<'a> Foo<'a> {
    pub fn func(&mut self, _s: &'a mut String) -> &'a str {
        "hello world"
    }
}

fn test_func(arg: &mut Foo) {
    let mut buf = String::new();
    let _s = arg.func(&mut buf);
}

这是为什么呢?因为

func
可以将源自其
self.0
参数的引用分配给
_s
。虽然 usize 中的
String
可能
(使用不安全代码),但如果我们将字段类型更改为 
&mut str
 (
playground),则更容易演示:

struct Foo<'a>(&'a mut str); impl<'a> Foo<'a> { pub fn func(&mut self, s: &'a mut String) -> &'a str { self.0 = s; "hello world" } }
你现在明白为什么

test_func

有问题了吗?当它返回时,
arg.0
将保存对已释放内存的引用!未定义的行为。

但是,

Foo::func

没有收到
&mut self
,而是收到了
&self
。借用检查员接受了这一点,因为非正式地,该字段的突变是不可能的。然而,当处于 
RefCell
 时,可以通过共享引用进行突变,因此调用再次被拒绝。

对这个非正式论证的更正式的处理,正如@kmdreko在上面的评论中所说,是通过

variance的方式:&'a mut T

'a
中是协变的,而
RefCell<&'a mut T>
'a
中是不变的;因此,
Foo
的生命周期参数分别是协变和不变的,
&Foo
也是如此(而
&mut Foo
是不变的)。有关更多信息,请参阅 
Rustonomicon 有关子类型和方差的章节。

© www.soinside.com 2019 - 2024. All rights reserved.