当输入非常清楚时,为什么借用检查器需要输出生命周期标签?

Why does borrow checker need life time tags for output when the inputs are very clear?

为什么借用检查器会对以下代码中的生命周期感到困惑

fn main() {
    let ss = "abc"; // lets say 'a scope
    let tt = "def"; // lets say 'b scope
    let result = func(ss, tt);
}    

fn func(s: &str, t: &str) -> &str {
    t
}
| fn func(s: &str, t: &str) -> &str {
|                              ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `s` or `t`

为什么这段代码中的内容如此重要?我是否遗漏了一些非常重要的边缘案例?

但是当我用生命周期标签注释它们时它起作用了。

fn func<'a>(s: &'a str, t: &'a str) -> &'a str {
    t
}

我读到每个变量绑定 (let) 都会创建一个隐式作用域,那么为什么 2 个输入变量具有相同的作用域。纠正我,如果我 worng。在函数调用'func'栈中,"s"会先被压入,然后"t",所以"s"和"t"的生命周期不同。首先删除 "t",然后删除 "s"。

你没有告诉编译器 return 值是否可以从 st 借用,或者两者都借用:

fn from_s<'a, 'b>(s: &'a str, t: &'b str) -> &'a str {
    // can be abbreviated: fn from_s<'a>(s: &'a str, t: &str) -> &'a str
    s
}

fn from_t<'a, 'b>(s: &'a str, t: &'b str) -> &'b str {
    // can be abbreviated: fn from_t<'a>(s: &str, t: &'a str) -> &'a str
    t
}

fn from_both<'a>(s: &'a str, t: &'a str) -> &'a str {
    if s < t {
        s
    } else {
        t
    }
}

fn from_neither<'a, 'b>(s: &'a str, t: &'b str) -> &'static str {
    // can be abbreviated: fn func(s: &str, t: &str) -> &'static str
    "foo"
}

如果你没有写 'static,编译器会假设最后一个不是你想要的。但是你仍然需要在前三个之间消除歧义。

要了解差异为何重要,请考虑像

这样的来电者
fn main() {
    let s = String::from("s");
    let r;
    {
        let t = String::from("t");
        r = from_s(&s, &t);
        // t goes out of scope
    }
    println!("{}", r);
}

如果编译器允许您调用 from_t 而不是 from_s,您将打印一个已经被释放的字符串。

如果我理解正确,问题是 "why both arguments may have the same lifetime?" 简短的回答是生命周期注释不是 具体 值,而是 范围 - 它表示 "this value must live no more/no less then this lifetime".

当您像问题中那样编写代码时:fn func<'a>(s: &'a str, t: &'a str) -> &'a str,您实际上是在说以下内容:

  • 有一些生命周期 - 让我们将其命名为 'a,每个调用站点上的生命周期可能不同。
  • 参数 st 必须同时存在 不少于 然后 'a (对于字符串文字,情况总是如此,因为它们是 'static,但这可能不适用于 &String 强制转换为 &str)——也就是说,函数类型是 contra 参数类型的变体(生命周期是类型的一部分)。
  • return 值必须 不再存在 然后 'a - 函数类型是 co 变体 return类型。

(有关方差的更多信息,请参阅 Rustonomicon

简化,这意味着两个参数都必须比 return 值长。这并不总是你想要的 - 考虑以下情况(请注意,我现在 returning s,因此初始化顺序不会改变):

fn main() {
    let ss = "abc";
    let mut result = "";
    {
        let tt = "def".to_string();
        result = func(ss, &tt);
    }
    println!("{}", result);
}    

fn func<'a>(s: &'a str, t: &'a str) -> &'a str {
    s
}

(playground)

此代码无法编译,尽管它在逻辑上是正确的,因为生命周期注释不符合逻辑:第二个参数 t 与 return 没有任何关系值,但根据函数注释,它限制了它的生命周期。但是当我们将函数更改为以下内容时:

fn func<'a, 'b>(s: &'a str, t: &'b str) -> &'a str {
    s
}

...它编译并 return 期望的结果(尽管有一些警告),因为现在生命周期 'b'a 无关,事实上,完全可以删除 - 生命周期省略会很好地发挥作用。