在 self 上指定生命周期时,Rust 在 trait 上使用动态多态性的问题

Issue with Rust using dynamic polymorphism on trait when specifying lifetime on self

我终于决定尝试一下 Rust(1.7 和 1.8)。来自 C++,我必须说 Rust 看起来很棒。我试图在 C++ 中重现一个众所周知的行为,该行为包括对一组对象使用动态多态性。

我遇到了一个我能够解决的非常烦人的问题,但我想知道你们对这个问题的看法,我希望它可以帮助其他正在尝试做同样问题的人

让我们考虑以下实现初始想法的代码:

struct Foo
{
    foo: u32,
}
trait Bar
{
    fn bar(& self) -> u32;
}
impl Bar for Foo
{
    fn bar(& self) -> u32 
    {
        return self.foo; 
    }
}
fn foobar(l: &std::collections::LinkedList<& Bar>)
{
    for i in l
    {
        println!("{}", i.bar());
    }
}
fn main()
{
    let foo0 = Foo{foo: 8u32};
    let foo1 = Foo{foo: 8u32};
    let mut l = std::collections::LinkedList::new();
    l . push_back(&foo0 as &Bar);
    l . push_back(&foo1 as &Bar);
    foobar(&l);
}

这里一切都在编译,一切都在完美地工作。

我想传递另一个对特征 Bar 的函数 bar 的引用,结果我不得不为 Bar 特征添加生命周期。为了简单起见,我将只添加生命周期(因为它将在 Rust 1.8 下很好地编译)。通过整个代码添加生命周期后,代码最终看起来像这样:

struct Foo
{
    foo: u32,
}
trait Bar<'a>
{
    fn bar(& 'a self) -> u32;
}
impl<'a> Bar<'a> for Foo
{
    fn bar(& 'a self) -> u32 
    {
        return self.foo; 
    }
}
fn foobar<'a>(l: &std::collections::LinkedList<& 'a Bar>)
{
    for i in l
    {
        println!("{}", i.bar());
    }
}
fn main()
{
    let foo0 = Foo{foo: 8u32};
    let foo1 = Foo{foo: 8u32};
    let mut l = std::collections::LinkedList::new();
    l . push_back(&foo0 as &Bar);
    l . push_back(&foo1 as &Bar);
    foobar(&l);
}

如果编译这段代码,错误信息如下:

../test.rs:21:12: 21:13 error: cannot infer an appropriate lifetime due to conflicting requirements [E0495]
../test.rs:21   for i in l
                         ^
../test.rs:19:1: 25:2 help: consider using an explicit lifetime parameter as shown: fn foobar<'a>(l: &std::collections::LinkedList<&'a Bar>)
../test.rs:19 fn foobar<'a>(l: &std::collections::LinkedList<& 'a Bar>)
../test.rs:20 {
../test.rs:21   for i in l
../test.rs:22   {
../test.rs:23     println!("{}", i.bar());
../test.rs:24   }
              ...
error: aborting due to previous error

到这里问题就很明显了,编译器知道push_back中给出的变量有不同的生命周期,因此它不能符合和不同意我写的。如果在同一个let语句中声明变量foo0foo1,问题就可以解决。

我发现很难找出该代码中的错误所在。 我的问题是:

如果将 foobar 的定义更改为:

,第二个代码示例将编译通过
fn foobar<'a>(l: &std::collections::LinkedList<&'a Bar<'a>>) {
    for i in l {
        println!("{}", i.bar());
    }
}

但是,你对生命周期参数所做的事情对我来说意义不大。

当我们想要一个return引用的方法时,我们通常会定义一个在整个生命周期内通用的特征(例如您的 Bar 的第二个版本)实施者的成员。例如,假设我们有以下结构:

struct Foo<'a> {
    foo: &'a str,
}

此结构包含一个引用,我们必须明确命名该生命周期。我们希望允许任何生命周期,所以我们定义了一个生命周期参数,'a.

我们可以给这个结构添加一个固有的方法:

impl<'a> Foo<'a> {
    fn foo(&self) -> &'a str {
        self.foo
    }
}

请注意 &self 没有明确的生命周期。相反,我们在 foo 方法的 return 类型上使用 impl 中的 'a 参数,因为我们希望 returned 值的生命周期为与结构中的生命周期相同,而不是 self 的生命周期(通常会更短)。

如果我们想类似地定义一个特征方法怎么办?

trait Bar<'a> {
    fn bar(&self) -> &'a str;
}

impl<'a> Bar<'a> for Foo<'a> {
    fn bar(&self) -> &'a str {
        self.foo
    }
}

特征 Bar 定义了生命周期参数,impl 将其链接到 Foo 的生命周期参数。

除了在特征上添加生命周期参数,您还可以在各个方法上添加生命周期参数。

例如,考虑这个特征:

trait Slice {
    fn slice<'a>(&self, s: &'a str) -> &'a str;
}

我们希望结果与 s 参数具有相同的生命周期。为此,我们需要在方法上定义一个生命周期参数并将其应用于两个引用。

为了完整起见,这里是该特征的一个实现:

struct SliceBounds {
    start: usize,
    end: usize,
}

impl Slice for SliceBounds {
    fn slice<'a>(&self, s: &'a str) -> &'a str {
        &s[self.start..self.end]
    }
}