添加无关的泛型参数会触发奇怪的生命周期错

Luk*_*odt 7 lifetime rust

我有一个特性,我想为所有实现的类型实现它std::ops::Index.这段代码有效(正如我所料):

use std::ops::Index;
use std::fmt::Display;


trait Foo {
    fn foo(&self, i: usize) -> &Display;
}

impl<C> Foo for C 
where
    C: Index<usize>,
    C::Output: Display + Sized,
{
    fn foo(&self, i: usize) -> &Display {
        &self[i]
    }
}
Run Code Online (Sandbox Code Playgroud)

(游乐场)

但是,一旦我将一个通用参数引入我的特性,我就会遇到奇怪的生命周期错误.这是代码(Playground):

trait Foo<T> {
    fn foo(&self, i: T) -> &Display;
}

impl<C, T> Foo<T> for C 
where
    C: Index<T>,
    C::Output: Display + Sized,
{
    fn foo(&self, i: T) -> &Display {
        &self[i]
    }
}
Run Code Online (Sandbox Code Playgroud)

而奇怪的错误(显然这是一个错误,在略有不同的版本中重复三次):

  error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn foo(&self, i: T) -> &Display {
15 | |         &self[i]
16 | |     }
   | |_____^
note: ...so that the type `<C as std::ops::Index<T>>::Output` is not borrowed for too long
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^

error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn foo(&self, i: T) -> &Display {
15 | |         &self[i]
16 | |     }
   | |_____^
note: ...so that the type `<C as std::ops::Index<T>>::Output` will meet its required lifetime bounds
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^

error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
  --> src/main.rs:15:10
   |
15 |         &self[i]
   |          ^^^^^^^
   |
   = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn foo(&self, i: T) -> &Display {
15 | |         &self[i]
16 | |     }
   | |_____^
note: ...so that the reference type `&<C as std::ops::Index<T>>::Output` does not outlive the data it points at
  --> src/main.rs:15:10
   |
15 |         &self[i]
   |          ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

我根本不明白这个错误.特别是因为错误说明其生命周期C::Output(据我所知)与附加参数无关K.

有趣的是,不返回特征对象&Display,但添加返回的关联类型Foo会使生命周期错误消失(Playground).但是,这对我来说不是解决方案.


这个错误是什么意思?是否有意义?这是编译器错误吗?参数K与生命周期有C::Output什么关系?

tre*_*tcl 6

它是有道理的,并不是编译器错误,但它有点不方便.

完整解释

这是可以实现Index<T>的类型C,使得C::Output其类型必须活得长一些的寿命是内部T.这是一个愚蠢的例子:

struct IntRef<'a>(&'a i32);

impl<'a, 'b: 'a> Index<IntRef<'a>> for IntRef<'b> {
    type Output = IntRef<'a>;
    fn index(&self, _: IntRef<'a>) -> &Self::Output {
        self
    }
}
Run Code Online (Sandbox Code Playgroud)

毯子impl将努力实现Foo<IntRef<'a>>IntRef<'b>,这是不合理的.要了解原因,请查看此非编译示例:

let b = 2i32; // 'b begins here:
let b_ref = IntRef(&b);
let o: &Display;  // a reference that outlives 'a but not 'b
{
    let a = 1i32; // 'a begins here:
    let a_ref = IntRef(&a);

    o = &b_ref[a_ref]; // <-- this errors: "a doesn't live long enough"
                       //     which is correct!
    o = b_ref.foo(a_ref); // <-- this wouldn't error, because the returned
                          //     value is `&'x (Display + 'x)` where 'x is
                          //     the lifetime of `b_ref`
}
println!("{:?}", o);
Run Code Online (Sandbox Code Playgroud)

o = &b_ref[a_ref];将不会编译因为Index实现b_ref[a_ref]不能超过a_ref.但o = b_ref.foo(a_ref) 必须编译,因为Foo<T>::foo... 的定义

fn foo(&self, i: T) -> &Display                   // what you wrote
fn foo<'a>(&'a self, i: T) -> &'a ('a + Display)  // what the compiler inferred
Run Code Online (Sandbox Code Playgroud)

...强制输出的生命周期取决于生命周期&self(参见此问题).编译器拒绝全面实现,Foo因为如果它被允许,你可以使用它来"扩大"生命周期,a_ref如上例所示.

(我无法想出一种IntRef实用的方法,但事实仍然是你可以做到这一点.可能,内部可变性,一个足够聪明的人可能会引入不健全这是允许的.)


解决方案0:快速而肮脏

只要求T永远不要包含任何(非'static)引用,你的工作就完成了.

impl<C, T> Foo<T> for C
where
    T: 'static,
    C: Index<T>,
    C::Output: Display + Sized,
Run Code Online (Sandbox Code Playgroud)

对于特征的最常见用途可能就是这种情况Index,但是如果你想要实现Foo<&T>(这不是不合理的),你会想要尝试一些限制性较小的东西.

另一种可能性是要求C::Output'static,但是这又是比需要更保守.

解决方案1:最佳方式

让我们回到以下方面Foo::foo:

fn foo<'a>(&'a self, i: T) -> &'a ('a + Display)
Run Code Online (Sandbox Code Playgroud)

注意两个人'a&'a ('a + Display).虽然它们是相同的,但它们代表不同的东西:返回引用的(最大)生命周期,以及被引用的东西中包含的任何引用的(最大)生命周期.

Index我们正在使用Foo的内容中,返回的引用的生命周期始终与借用相关联&self.但是Self::Output可能包含具有不同(可能更短)生命周期的其他引用,这是整个问题.所以我们真正想写的是......

fn foo(&self, i: T) -> &('a + Display)            // what you write
fn foo<'b>(&'b self, i: T) -> &'b ('a + Display)  // what the compiler infers
Run Code Online (Sandbox Code Playgroud)

......将生命中的生命与&self内在的生命时间分离开来Self::Output.

当然现在的问题'a是没有在任何地方定义特征,所以我们必须将其添加为参数:

trait Foo<'a, T> {
    fn foo(&self, i: T) -> &('a + Display);
}
Run Code Online (Sandbox Code Playgroud)

现在你可以告诉锈病是C::Output必须活得比'aimpl申请,一切都将被罚款(操场):

impl<'a, C, T> Foo<'a, T> for C
where
    C: Index<T>,
    C::Output: 'a + Display + Sized,
{
    fn foo(&self, i: T) -> &('a + Display) {
        &self[i]
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方案2:将绑定放在方法上

解决方案1要求您添加生命周期参数Foo,这可能是不合需要的.另一种可能性是添加一个where条款,foo这需要T比返回的更长&Display.

trait Foo<T> {
    fn foo<'a>(&'a self, i: T) -> &'a Display where T: 'a;
}
Run Code Online (Sandbox Code Playgroud)

它有点笨拙,但实际上它可以让你将需求转移到函数而不是特征本身.缺点是,这Foo通过坚持返回值永远不会超过任何引用来排除某些实现T.