限制借用 impl Trait 对象的生命周期
Constraining lifetimes of a borrow of an impl Trait object
考虑以下代码:
trait MyTrait<'a, 'b: 'a> {
fn f(&'b mut self) -> &'a str;
}
struct MyStruct {
my_string: String,
}
impl<'a, 'b: 'a> MyTrait<'a, 'b> for MyStruct {
fn f(&'b mut self) -> &'a str {
&self.my_string
}
}
fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
bar(s);
bar(s); // ERROR
}
fn foo2<'a>(s: &mut MyStruct) {
bar(s);
bar(s);
}
fn bar<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
let x = s.f();
}
特征 MyTrait
定义了一个 returns 一个 str
引用的函数。它的生命周期可以解释为 “返回 str
引用的对象将至少与该引用一样长。”.
bar
是一个函数,它接受对实现 MyTrait
并调用 f
的结构的可变引用。函数 foo
和 foo2
相同,唯一的区别是 foo2
接受特定类型,而 foo
接受实现 MyTrait
的任何具体类型。
由于 foo2
可以编译,我希望 foo
也可以编译,但没有。这是错误消息:
error[E0499]: cannot borrow `*s` as mutable more than once at a time
--> src\bin\main2.rs:17:6
|
15 | fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
| -- lifetime `'b` defined here
16 | bar(s);
| ------
| | |
| | first mutable borrow occurs here
| argument requires that `*s` is borrowed for `'b`
17 | bar(s); // ERROR
| ^ second mutable borrow occurs here
据我了解,这是正在发生的事情:MyTrait
强制实现它的对象在生命周期 'b
中存在。对 bar
的第一次调用在整个生命周期 'b
中借用 s
,这意味着借用不会像常规借用那样在 bar
结束时丢弃,而只会丢弃在 'b
结束时。 foo
和 bar
具有相同的函数签名,因此指定的生命周期将相同。这意味着当 bar
可变地借用 s
时,这个借用只会在 'b
结束时超出范围 - 在 foo
结束时。第二个 bar
调用将遵循相同的逻辑,但是由于已经存在一个可变借用,编译器将不允许创建另一个。
最初我认为一个解决方案是将 bar
的签名重写为:
fn bar<'a, 'b: 'a, 'c: 'b>(s: &'b mut impl MyTrait<'a, 'c>)
希望编译器将其解释为:'b
仅在函数的持续时间内存在,'a
甚至受到进一步限制,对象本身 *s
的生命周期 'c
超过函数 的生命周期。
但是它导致了错误:
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
是否可以限制 bar
中的借用仅在 bar
期间有效?
我不确定你是怎么得到你发布的代码的,但我发现最好的经验法则是从没有明确的生命周期参数开始,并且只在编译器说需要它们时才添加它们。
在你的情况下,编译器不需要生命周期参数,很高兴自己把它们全部弄清楚:
trait MyTrait {
fn f(&mut self) -> &str;
}
struct MyStruct {
my_string: String,
}
impl MyTrait for MyStruct {
fn f(&mut self) -> &str {
&self.my_string
}
}
fn foo(s: &mut impl MyTrait) {
bar(s);
bar(s);
}
fn foo2(s: &mut MyStruct) {
bar(s);
bar(s);
}
fn bar(s: &mut impl MyTrait) {
let x = s.f();
}
特别是,对于任何接受一个引用并返回一个引用的函数,编译器可以计算出返回的引用必须来自参数,因此不需要明确的界限.它实际上在做什么(AFAIK)基本上是插入这些生命周期:
trait MyTrait {
fn f<'a>(&'a mut self) -> &'a str;
}
impl MyTrait for MyStruct {
fn f<'a>(&'a mut self) -> &'a str {
&self.my_string
}
}
请注意 (1),生命周期在 函数 上而不是在特征上。这是添加生命周期参数时的另一个好的经验法则,始终将它们放在尽可能最具体的范围内。还有(2),不需要两个参数。
编辑:
至于理解为什么它不特别喜欢你的版本,我认为这基本上是因为生命周期是在特征而不是函数上定义的。 MyTrait
上的生命周期 'b
特别是 类型 的特征,而不是任何特定代码路径的特征。就编译器所知,对于泛型 MyTrait<'a, 'b>
,这些生命周期可能完全与代码中其他地方的其他内容相关联,它无法知道 'b
无法比您对 foo
的调用更长久因为它必须对所有可能的 'b
s.
通用
但在 foo2
中,编译器可以看到 MyStruct
的所有生命周期,它可以将这些生命周期折叠到它拥有的具体情况,并且知道它只需要借用 s
持续 bar
.
考虑以下代码:
trait MyTrait<'a, 'b: 'a> {
fn f(&'b mut self) -> &'a str;
}
struct MyStruct {
my_string: String,
}
impl<'a, 'b: 'a> MyTrait<'a, 'b> for MyStruct {
fn f(&'b mut self) -> &'a str {
&self.my_string
}
}
fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
bar(s);
bar(s); // ERROR
}
fn foo2<'a>(s: &mut MyStruct) {
bar(s);
bar(s);
}
fn bar<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
let x = s.f();
}
特征 MyTrait
定义了一个 returns 一个 str
引用的函数。它的生命周期可以解释为 “返回 str
引用的对象将至少与该引用一样长。”.
bar
是一个函数,它接受对实现 MyTrait
并调用 f
的结构的可变引用。函数 foo
和 foo2
相同,唯一的区别是 foo2
接受特定类型,而 foo
接受实现 MyTrait
的任何具体类型。
由于 foo2
可以编译,我希望 foo
也可以编译,但没有。这是错误消息:
error[E0499]: cannot borrow `*s` as mutable more than once at a time
--> src\bin\main2.rs:17:6
|
15 | fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
| -- lifetime `'b` defined here
16 | bar(s);
| ------
| | |
| | first mutable borrow occurs here
| argument requires that `*s` is borrowed for `'b`
17 | bar(s); // ERROR
| ^ second mutable borrow occurs here
据我了解,这是正在发生的事情:MyTrait
强制实现它的对象在生命周期 'b
中存在。对 bar
的第一次调用在整个生命周期 'b
中借用 s
,这意味着借用不会像常规借用那样在 bar
结束时丢弃,而只会丢弃在 'b
结束时。 foo
和 bar
具有相同的函数签名,因此指定的生命周期将相同。这意味着当 bar
可变地借用 s
时,这个借用只会在 'b
结束时超出范围 - 在 foo
结束时。第二个 bar
调用将遵循相同的逻辑,但是由于已经存在一个可变借用,编译器将不允许创建另一个。
最初我认为一个解决方案是将 bar
的签名重写为:
fn bar<'a, 'b: 'a, 'c: 'b>(s: &'b mut impl MyTrait<'a, 'c>)
希望编译器将其解释为:'b
仅在函数的持续时间内存在,'a
甚至受到进一步限制,对象本身 *s
的生命周期 'c
超过函数 的生命周期。
但是它导致了错误:
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
是否可以限制 bar
中的借用仅在 bar
期间有效?
我不确定你是怎么得到你发布的代码的,但我发现最好的经验法则是从没有明确的生命周期参数开始,并且只在编译器说需要它们时才添加它们。
在你的情况下,编译器不需要生命周期参数,很高兴自己把它们全部弄清楚:
trait MyTrait {
fn f(&mut self) -> &str;
}
struct MyStruct {
my_string: String,
}
impl MyTrait for MyStruct {
fn f(&mut self) -> &str {
&self.my_string
}
}
fn foo(s: &mut impl MyTrait) {
bar(s);
bar(s);
}
fn foo2(s: &mut MyStruct) {
bar(s);
bar(s);
}
fn bar(s: &mut impl MyTrait) {
let x = s.f();
}
特别是,对于任何接受一个引用并返回一个引用的函数,编译器可以计算出返回的引用必须来自参数,因此不需要明确的界限.它实际上在做什么(AFAIK)基本上是插入这些生命周期:
trait MyTrait {
fn f<'a>(&'a mut self) -> &'a str;
}
impl MyTrait for MyStruct {
fn f<'a>(&'a mut self) -> &'a str {
&self.my_string
}
}
请注意 (1),生命周期在 函数 上而不是在特征上。这是添加生命周期参数时的另一个好的经验法则,始终将它们放在尽可能最具体的范围内。还有(2),不需要两个参数。
编辑:
至于理解为什么它不特别喜欢你的版本,我认为这基本上是因为生命周期是在特征而不是函数上定义的。 MyTrait
上的生命周期 'b
特别是 类型 的特征,而不是任何特定代码路径的特征。就编译器所知,对于泛型 MyTrait<'a, 'b>
,这些生命周期可能完全与代码中其他地方的其他内容相关联,它无法知道 'b
无法比您对 foo
的调用更长久因为它必须对所有可能的 'b
s.
但在 foo2
中,编译器可以看到 MyStruct
的所有生命周期,它可以将这些生命周期折叠到它拥有的具体情况,并且知道它只需要借用 s
持续 bar
.