缺少终身运营商

Missing Lifetime Operator

我在 Rust 中有以下代码。我知道我不应该 return 引用局部变量,在这种情况下我不是。要拆分的字符串作为 &str 引用传递,在确定拆分边界后,我 returning &s[0..idx] 其中 idx 是边界的末端。我相信这不会导致 "dangling" 参考相关错误。然而,事实证明我错了!

fn demo4() {
    let mut s = String::from("Elijah Wood");
    let firstname = str_split(&s, &String::from(" "));
    println!("First name of actor: {}", firstname);
}
// can handle both &str and &String
fn str_split(s: &str, pat: &str) -> &str {
    let bytes = s.as_bytes();
    let b_pat = pat.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b_pat {
            return &s[0..i];
        }
    }
    &s[..]
}

fn main() {
    demo4();
}

我收到以下错误:

error[E0106]: missing lifetime specifier
 --> src/main.rs:7:37
  |
7 | fn str_split(s: &str, pat: &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 `pat`

非常感谢任何解释。

错误消息会告诉您出了什么问题,但不会告诉您如何修复它:

  = help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `s` or `pat`

编译器使用生命周期来确定代码是否安全。其中一部分是了解每个参考文献可以借鉴的内容。签名:

fn str_split(s: &str, pat: &str) -> &str

不表示 str_split returns 是对 s 的引用还是对 pat 的引用,因此 Rust 无法判断如何检查参考。 (另请参阅 this question 了解其中函数根本没有引用参数的版本。)

要解决这个问题,你需要引入一个生命周期参数:

fn str_split<'a>(s: &'a str, pat: &str) -> &'a str

粗略地说,“如果你借用一个字符串一段时间 'a,你可以调用 str_split 它(和另一个字符串)并取回一个同样有效的引用 'a =17=]。” &pat没有注解'a,因为结果没有借自pat,只借自s.

Rust 编程语言 has a chapter on lifetimes 解决了这个问题,我强烈建议您阅读它; Rust 的生命周期不仅仅是防止悬挂指针。


虽然不是问题的一部分,但这个函数的主体是一行。除非这纯粹是一个学习练习,否则不要做比你必须做的更多的工作:

fn str_split<'a>(s: &'a str, pat: &str) -> &'a str {
    s.split(pat).next().unwrap_or(s)
}

&str 是 shorthand for &'a str,其中 'a 是一些需要事先声明的生命周期参数。在一些简单的情况下。可以省略这些生命周期参数,编译器会为您扩展它。但是,在某些情况下,您需要明确声明生命周期。

来自 The Rust Programming Language, Second Edition(强调我的),这里是关于省略生命周期参数的规则:

  1. Each parameter that is a reference gets its own lifetime parameter. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32), a function with two arguments gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32), and so on.

  2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.

  3. If there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, then the lifetime of self is assigned to all output lifetime parameters. This makes writing methods much nicer.

你的函数的问题在于它有两个输入生命周期参数,因此编译器不会为你选择一个。你必须这样写你的函数:

fn str_split<'a>(s: &'a str, pat: &str) -> &'a str {
    s
}

如果您不熟悉此语法,请务必阅读 the chapter on lifetimes

为什么编译器不能自己解决?因为 Rust 有一个原则,一个函数的签名不应该因为它的实现发生变化而改变。它简化了编译器(它不必处理签名尚未完全确定的相互依赖的函数),还简化了您自己代码的维护。例如,如果您要像这样更改函数的实现:

fn str_split(s: &str, pat: &str) -> &str {
    pat
}

那么输出的生命周期参数必须链接到 pat 的生命周期参数。在图书馆里,这是一个突破性的变化;您不希望重大更改在您不注意的情况下溜走!

感谢大家解释错误及其背后的原因。我已经修复了代码并做了一些我想解释的更改。首先感谢@trentcl 指出模式匹配在语义上是错误的。原因是搜索模式是通过匹配数组中的每个字节而不是整个数组本身来完成的。这促使我通过拆分第一次出现的 space 个字符 ' ' 来将函数更改为仅 return 个单词。 此外,函数签名需要包含生命周期特征才能正确编译。工作代码如下:

// 4 Demo with string spliting
fn demo4() {
     let s = String::from("Elijah Wood");
     let firstname = str_split(&s);
     println!("First name of actor: {}", firstname);
}
// splits a string at first space
fn str_split<'a>(s : &'a str) -> &'a str {
    let bytes = s.as_bytes();
    for(i, &item) in bytes.iter().enumerate() {
         if item == b' ' {
             return &s[0..i];
         }
    }
    &s[..]
}