添加不相关的通用参数会触发奇怪的生命周期错误



我有一个特征,我想为所有实现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]
}
}

(游乐场(

但是,一旦我在我的特征中引入了一个通用参数,我就会得到一个奇怪的生命周期错误。这是代码(游乐场(:

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]
}
}

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

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]
|          ^^^^^^^

我完全不明白这个错误。特别是因为该错误谈论了C::Output的生命周期,据我了解,这与附加参数K无关

有趣的是,不返回 trait 对象&Display,而是将关联的类型添加到返回的Foo,会使生存期错误消失(Playground(。但是,这对我来说不是解决方案。


此错误是什么意思?有意义吗?是编译器错误吗?参数KC::Output的寿命有什么关系?

这是有道理的,并且不是编译器错误,但有些不方便。

完整解释

可以为类型实现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
}
}

一揽子impl会试图为IntRef<'b>实施Foo<IntRef<'a>>,这是不合理的。要了解原因,请查看以下非编译示例:

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);

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

。强制输出的生存期取决于&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,

对于Index特征的最常见用法来说,这可能是这种情况,但是如果您希望能够实现Foo<&T>(这并非不合理(,您将需要尝试一些限制较少的东西。

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

解决方案1:最佳方法

让我们回到Foo::foo的脱糖:

fn foo<'a>(&'a self, i: T) -> &'a ('a + Display)

请注意&'a ('a + Display)中的两个'a。尽管它们是相同的,但它们表示不同的东西:返回的引用的(最大(生存期,以及被引用的事物中包含的任何引用的(最大(生存期。

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

。它将&self的生命周期与任何可能是Self::Output内部的生命周期分离。

当然,现在的问题是'a没有在任何地方定义 trait 中,因此我们必须将其添加为参数:

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

现在你可以告诉 Rust,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]
}
}

解决方案 2:将边界放在方法上

解决方案 1 要求您向Foo添加一个生命周期参数,这可能是不可取的。另一种可能性是在foo中添加一个where子句,要求T比返回的&Display寿命更长。

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

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

最新更新