Rust 借用检查器无法推断生命周期的情况是什么?

3
在大多数情况下,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)
   ...
}

2
Rust生命周期说明符需要存在于像引用这样的东西中,这些东西始终与具有生命周期的其他东西相关联,即使该生命周期是“静态”的。我不确定您所说的“更少范围”的含义是什么。随着时间的推移,Rust似乎越来越擅长推断生命周期,因此在未来的Rust版本中,整个'a可能会消失,但目前它是必需的。它似乎也提醒人们这些生命周期的存在。 - tadman
1
我不确定我完全理解你在询问关于作用域和生命周期方面的问题。 - tadman
1
请注意,函数声明中的显式生命周期也充当文档,以便希望使用函数的人知道结果可能会从xy借用,而无需阅读代码。 - Jmb
1
@tadman编译器确实变得更加智能,可以对实际满足生命周期的代码进行推理,但编译器还不够聪明,无法理解这一点。据我所知,目前没有计划从函数体中推断生命周期,因此它们将被省略在函数签名中。这可能是未来语言的一个有效目标,提供类似的借用检查功能,但据我所知,Rust并不朝着这个方向发展- 生命周期已经成为了必须保留的特性。 - user4815162342
1
好的,我明白了。我猜这是那种情况之一,你的系统语言必须在简单性、效率和安全性之间做出选择,但只能选择其中两个。C++ 在效率和简单性方面表现良好(就生命周期而言),但非常不安全;Swift 和 Go 简单且安全,但效率不如前者;Rust 高效且安全,但不像前两者那么简单。 - user4815162342
显示剩余5条评论
1个回答

5

我认为您的问题处理方式不正确。当编译像您展示的 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

没有生命周期注释,我们无法仅从声明中判断返回的 &str 是来自第一个 &str、第二个 &&str、公共生命周期还是可能是静态 &&str。 对于非常简单的函数(如接受并返回单个引用的函数),编译器执行“lifetime elision”,其中机械地选择未注释签名的“明显”解释,允许您将 fn longest(x: &str) -> &str 写成 fn longest<'a>(x: &'a str) -> &'a str 的简单快捷方式。 但是当函数接受多个引用时,编译器拒绝猜测并要求您明确表述您想要的。

正如本答案开头所指出的那样,这经常使初学者感到困惑,编译器绝对拒绝从函数体推断生命周期签名,因为这会使签名依赖于实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接