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

时间:2016-01-25 21:21:41

标签: list polymorphism rust self traits

我终于决定尝试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中给出的变量有不同的生命周期,因此它不符合我的意思并且不同意我所写的内容。如果变量foo0foo1在同一let语句中声明,则可以解决该问题。

我发现很难弄清楚代码中出了什么问题。 我的问题是:

  • 有没有办法告诉编译器集合可能需要不同的生命周期?

  • 通过在另一个引用变量(此处未显示)而不是'a上设置self生命周期,代码将进行编译。这是否意味着如果我们没有指定任何生命周期,那么编译器会将'_与我之前提出的问题中的特定生命周期相对应?

  • 是否有隐含的规则&#34;禁止&#34;我们在自己身上度过一生?

  • 这段代码是惯用的Rust吗?

1 个答案:

答案 0 :(得分:4)

如果您将foobar的定义更改为:

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

然而,你对生命周期参数所做的事情对我来说并没有多大意义。

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

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

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

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

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

注意&self没有明确的生命周期。相反,我们在'a方法的返回类型上使用impl中的foo参数,因为我们希望返回值的生命周期与struct中的生命周期相同,而不是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]
    }
}