如何使用结构体和实现的生命周期来推断实现的适当生命周期?

3

我该如何解决这个错误?当我在impl中使用“匿名生命周期”时,我具体告诉编译器什么?

struct LineHandlerInfo<'a> {
    label: &'a str,
    match_literal: &'a str,
    f: fn(&str) -> Option<&str>,
}

struct Game<'a> {
    handlers: Vec<LineHandlerInfo<'a>>,
}

impl Game<'_> {
    fn match_str<'a>(
        &'a mut self,
        label: &'a str,
        match_literal: &'a str,
        mut f: fn(&str) -> Option<&str>,
    ) {
        let mut lh = LineHandlerInfo {
            label,
            match_literal,
            f,
        };
        self.handlers.push(lh);
    }
}

fn main() {
    let mut g = Game {
        handlers: Vec::new(),
    };
    g.match_str("echo hello", "hello", |s| {
        println!("{}", s);
        None
    });
}


当我尝试编译时,出现以下错误:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/main.rs:18:22
   |
18 |         let mut lh = LineHandlerInfo {
   |                      ^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the method body at 12:18...
  --> src/main.rs:12:18
   |
12 |     fn match_str<'a>(
   |                  ^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:19:13
   |
19 |             label,
   |             ^^^^^
note: but, the lifetime must be valid for the lifetime '_ as defined on the impl at 11:11...
  --> src/main.rs:11:11
   |
11 | impl Game<'_> {
   |           ^^
   = note: ...so that the expression is assignable:
           expected LineHandlerInfo<'_>
              found LineHandlerInfo<'_>

我如何解决这个错误?当我在结构体上已经有生命周期时,如果我在impl Game上指定一个生命周期,我到底在告诉编译器什么?


基于@shepmaster的链接和大量试错,我已经修复了编译器错误;https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=208beb18e2530b97f5ccf15102790a63 -- 很希望能够有更多关于这个语法究竟要传达什么的参考资料。 - Ultrasaurus
我删除了另一个变量,@shepmaster说这是一个不同的问题 - 这里留作参考:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=00449a690ba597eccc405ef1f8a951a8) - Ultrasaurus
2个回答

13
我怎样解决这个错误?当我在impl Game上指定一个生命周期时,我对编译器传达了什么信息?我猜测你的困惑源于对Rust中声明和使用生命周期方式的不完全理解。
结构体生命周期
为了在结构体上使用生命周期,你需要在声明结构体名称的< > 中声明生命周期,然后在结构体定义中引用它。重要的是要注意,在那里声明的生命周期仅适用于结构体定义 - 在外部没有意义。
例如(使用@Shepmaster提供的MRE):
struct Game<'a> {
    handlers: Vec<&'a str>,
}

结构体Game包含对字符串的引用向量,并且被引用的字符串必须至少与Game结构体同寿命。

实现生命周期

在实现块上使用生命周期限定符时,你需要在紧靠着 impl 关键字的 <> 内声明此生命周期。之后,你可以在被实现的结构体内部以及实现块内部引用这个生命周期,像这样:

impl<'b> Game<'b> {
    fn match_str(&mut self, label: &'b str) {
        self.handlers.push(label);
    }
}

请注意,我在这里使用完全不同的生命周期名称('b)来说明结构体上的生命周期声明与实现块上的生命周期声明是相互独立的。

分解一下:

impl<'b>

这意味着我们正在为一个结构体定义实现,在该定义内部我们将使用生命周期'b

 Game<'b> {

这意味着该实现针对具有生命周期'b的结构体Game - 因此,对于此实现内部的任何对self的引用也将自动具有生命周期'b

fn match_str(&mut self, label: &'b str) {

在这里,我们定义了一个名为match_str的方法,它接受一个名为label的参数。 label是一个字符串切片,它也具有生命周期'b - 因此它必须至少持续与调用该方法的self相同的时间。

在您的原始代码中,您可能有类似以下内容的代码:

impl Game<'_> {
    fn match_str<'a>(&mut self, label: &'a str) {
    ...
    }
}

这个代码告诉编译器:

  • 你正在开始一个新的实现块,并且在impl层面没有声明生命周期
  • 这个实现是为结构体Game定义的,该结构体有一个生命周期参数,但我们不关心它,并且不打算将其与实现的任何元素绑定
  • 我们正在定义一个名为match_str的方法,并声明了一个生命周期'a,我们可以在函数签名的其余部分中引用它
  • 我们有一个参数label,它具有生命周期a,但我们没有将此生命周期与其他任何内容相关联

更多信息:


1
精彩的解释。提供的细节分析给了我所需的信息。同时有文档参考也非常好。我之前没有注意到impl生命周期表示这个实现内部的self将自动具有impl生命周期。 - Ultrasaurus
@harmic的解释具有颠覆性。关于最后一点“但我们没有将此生命周期与其他任何东西相关联”的一个小问题。如果match_str返回一个引用(并且我们指定了生命周期a),那么这意味着我们正在将返回值的生命周期与label:&'a str相关联吗? - Haris Muzaffar
@HarisMuzaffar 是的,如果我们有这个 fn match_str<'a>(&mut self, label: &'a str) -> &'a str,那么返回值的生命周期不会超过 label 参数。还要注意,如果你从一个方法中返回一个引用并且没有注明它的生命周期,那么它将自动具有与 self 相同的生命周期(这被称为生命周期省略)。 - harmic

1

我该如何解决这个错误?

从函数中移除通用生命周期,为impl块提供一个生命周期名称而不是使用匿名生命周期,然后在函数参数中使用该命名生命周期。从&self中删除生命周期:

impl<'a> Game<'a> {
    fn match_str(&mut self, label: &'a str, match_literal: &'a str, f: fn(&str) -> Option<&str>) {
        self.handlers.push(LineHandlerInfo {
            label,
            match_literal,
            f,
        });
    }
}

另请参阅:

当我在impl中使用“匿名生命周期”时,我到底在做什么?

你实际上是在声明:“我知道这里有一个生命周期,但我不关心它”。但是,对于你的情况,这并不是真的;你需要关心参数化类型的生命周期,因为那是你的变量需要匹配的。

另请参阅:

对于一个包含函数指针的结构体

这与函数指针无关。在编程时遇到问题时,建议创建一个最小化的可重现示例,剥离不必要的内容以消除错误。这样可以专注于手头的问题。例如,以下是重现相同错误的示例:
struct Game<'a> {
    handlers: Vec<&'a str>,
}

impl Game<'_> {
    fn match_str<'a>(&mut self, label: &'a str) {
        self.handlers.push(label);
    }
}

@Ultrasaurus,有更多Rust特定的MRE技巧可供您使用,以缩小您在此处发布原始代码的规模。 - Shepmaster
我仍然不明白这个例子到底是什么意思,意味着匿名生命周期无法工作。对于其他结构体具有命名生命周期的示例,不需要为impl指定命名生命周期。 - Ultrasaurus
提供的独立示例在此答案中显示了不同的错误。我尝试应用修复方法,但仍然看到相同的错误: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c7b05b72fcaf32e3dde9b761661ce556 - Ultrasaurus
你没有应用相同的修复方法。这个函数不应该有任何通用的生命周期。 - Shepmaster
让我们在聊天中继续这个讨论 - Ultrasaurus
显示剩余2条评论

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