什么时候无法推断 Rust 借用检查器中的生命周期?
When it is not possible to infer the lifetime in the Rust borrow checker?
在大多数情况下,Rust 编译器可以推断出生命周期。如果在运行时确定生命周期范围,则表示必须显式标记生命周期。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
这里,
- 生命周期是通用的。
- 这意味着在函数结果返回后有一个生命周期'a绑定的范围。
- 编译器可以知道内存在最小生命周期'a内有效的信息。
我很好奇。而不是使用生命周期语法,编译器不能只采用生命周期 'a 可以绑定到的较小范围区域吗?
fn main() { //larger scope
let s1 = String::from("long string is long");
{ //fewer scope
let s2 = String::from("xyz");
let result = longest(s1.as_str(), s2.as_str());
println!("The longest string is {}", result);
}
}
即使调用方的调用栈比较复杂,作用域区域在借用的时候就确定了,所以同题好像也可以
fn func1<'a>(x: &'a str, y: &'a str) {
let c = String::from("hello");
let result = func2 (a,b,c)
...
}
我认为您以错误的方式处理问题。当编译一个函数时,例如你展示的 main()
,借用检查器不会检查它调用的单个函数的 内容 ,比如 longest()
,它只检查他们的签名。这是一个特性:它允许在不影响其签名提供的保证的情况下更改函数的实现。如果 main()
成功编译,你可以肯定它会继续编译,但是你修改 longest
只要你不改变它的声明。
在 longest()
的情况下,未注释的签名不明确:
fn longest(x: &str, y: &str) -> &str
// does the above mean:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str // returns sub-slice of x
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'b str // returns sub-slice of y
fn longest<'c> (x: &'c str, y: &'c str) -> &'c str // returns sub-slice outlived by x and y
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'static str // returns static data
如果没有生命周期注释,我们无法仅通过查看声明来判断 returned &str
是否来自第一个 &str
,第二个 &str
,共同的生命周期,或者可能是静态的 &str
。对于非常简单的函数,比如那些接受和 return 单个引用的函数,编译器执行“lifetime elision”,它会机械地选择未注释签名的“明显”解释,允许您编写 fn longest(x: &str) -> &str
作为 fn longest<'a>(x: &'a str) -> &'a str
的简单 shorthand。但是当一个函数接受多个引用时,编译器拒绝猜测并让你拼出你想要的。
正如答案开头所指出的,这常常让初学者感到困惑,编译器绝对拒绝做的是从函数体中推断生命周期签名,因为那会使签名依赖于实现。
在大多数情况下,Rust 编译器可以推断出生命周期。如果在运行时确定生命周期范围,则表示必须显式标记生命周期。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
这里,
- 生命周期是通用的。
- 这意味着在函数结果返回后有一个生命周期'a绑定的范围。
- 编译器可以知道内存在最小生命周期'a内有效的信息。
我很好奇。而不是使用生命周期语法,编译器不能只采用生命周期 'a 可以绑定到的较小范围区域吗?
fn main() { //larger scope
let s1 = String::from("long string is long");
{ //fewer scope
let s2 = String::from("xyz");
let result = longest(s1.as_str(), s2.as_str());
println!("The longest string is {}", result);
}
}
即使调用方的调用栈比较复杂,作用域区域在借用的时候就确定了,所以同题好像也可以
fn func1<'a>(x: &'a str, y: &'a str) {
let c = String::from("hello");
let result = func2 (a,b,c)
...
}
我认为您以错误的方式处理问题。当编译一个函数时,例如你展示的 main()
,借用检查器不会检查它调用的单个函数的 内容 ,比如 longest()
,它只检查他们的签名。这是一个特性:它允许在不影响其签名提供的保证的情况下更改函数的实现。如果 main()
成功编译,你可以肯定它会继续编译,但是你修改 longest
只要你不改变它的声明。
在 longest()
的情况下,未注释的签名不明确:
fn longest(x: &str, y: &str) -> &str
// does the above mean:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str // returns sub-slice of x
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'b str // returns sub-slice of y
fn longest<'c> (x: &'c str, y: &'c str) -> &'c str // returns sub-slice outlived by x and y
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'static str // returns static data
如果没有生命周期注释,我们无法仅通过查看声明来判断 returned &str
是否来自第一个 &str
,第二个 &str
,共同的生命周期,或者可能是静态的 &str
。对于非常简单的函数,比如那些接受和 return 单个引用的函数,编译器执行“lifetime elision”,它会机械地选择未注释签名的“明显”解释,允许您编写 fn longest(x: &str) -> &str
作为 fn longest<'a>(x: &'a str) -> &'a str
的简单 shorthand。但是当一个函数接受多个引用时,编译器拒绝猜测并让你拼出你想要的。
正如答案开头所指出的,这常常让初学者感到困惑,编译器绝对拒绝做的是从函数体中推断生命周期签名,因为那会使签名依赖于实现。