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
尽管没有修改函数签名,但为什么此更改会导致借用检查器失败?感谢您的帮助!
你的例子并不是真正等价的,因为在第一种情况下
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 有关子类型和方差的章节。