"Self" 和省略生命周期之间的区别

Difference between "Self" and elided lifetime

不知道如何描述,但这是最小的复制片段(也在 playground 上):

struct Ctx(Vec<Box<dyn Fn(&mut MockCtx)>>);

struct MockCtx<'a>(&'a mut Ctx);

impl MockCtx<'_> {
    // this works
    fn push<F: 'static + Fn(&mut MockCtx)>(&mut self, f: F) {
        self.0.0.push(Box::new(f));
    }
}

trait Push {
    fn push<F: 'static + Fn(&mut Self)>(&mut self, f: F);
}

impl Push for MockCtx<'_> {
    // fn push<F: 'static + Fn(&mut Self)>(&mut self, f: F) {    (1)
    fn push<F: 'static + Fn(&mut MockCtx)>(&mut self, f: F) { // (2)
        MockCtx::push(self, f)
    }
}

如果我采用 (1),编译器会报告

expected a `Fn<(&mut MockCtx<'_>,)>` closure, found `F`

并建议添加明确的生命周期限制。如果我这样做或只是采用 (2),编译器报告

impl has stricter requirements than trait

事实是我从一开始就不明白这个问题...我明白什么是生命周期和简单的“寿命”规则,对我来说编译器应该不用担心这些代码 'a 所以很远,因为只有当这些 Box<dyn Fn> 实际被调用时,这才是一回事。

感谢您的回复和解释!

这有点不透明是可以理解的,因为省略了几个生命周期。写出来应该会更清楚一些。

先来看看容器:

struct Ctx(Vec<Box<dyn Fn(&mut MockCtx)>>);

这是以下简称:

struct Ctx(Vec<Box<dyn for<'a, 'b> Fn(&'a mut MockCtx<'b>) + 'static>>);

'static 要求您放入容器中的任何闭包都必须拥有其数据。它不能引用依赖于生命周期的其他数据。 elided lifetime is 'static 因为 Box 没有生命周期参数。

结构for<'a, 'b> Fn(&'a mut MockCtx<'b>)higher rank trait bound,简称HRTB。这意味着您放入容器中的任何闭包都必须能够接受具有任何生命周期的可变引用,指向具有任何生命周期参数的 MockCtx<'_> 。稍后注意,这里有两个生命周期参数。

现在,让我们进入实现:

impl MockCtx<'_> {
    fn push<F: 'static + Fn(&mut MockCtx)>(&mut self, f: F);
}

这是以下简称:

impl MockCtx<'_> {
    fn push<F: 'static + for<'a, 'b> Fn(&'a mut MockCtx<'b>)>(&mut self, f: F);
}

对密封件的要求与对容器的要求相同。没有冲突。但请注意,闭包必须接受 除了 Self 之外的类型。它必须接受对具有不同生命周期的 MockCtx<'_> 的可变引用。

现在让我们来看看特征:

trait Push {
    fn push<F: 'static + Fn(&mut Self)>(&mut self, f: F);
}

这是以下简称:

trait Push {
    fn push<F: 'static + for<'a> Fn(&'a mut Self)>(&mut self, f: F);
}

这会发生冲突。给 Push 的闭包只能在任何生命周期中通过引用 Self 来调用。但是我们存储在容器中的闭包必须接受 Self 以外的一些类型。有一整套类似 Self 的类型,它们具有不同的生命周期参数,闭包需要为我们的容器接受这些参数。

现在,我们可以解释为什么两次尝试都失败了。 #1 失败是因为 trait 和 trait impl 中的闭包只能接受 Self,但我们试图将其存储在一个容器中,该容器包含可以接受任何生命周期的 MockCtx<'_> 的闭包。 #2 失败是因为 trait 只需要闭包接受 Self,但是我们的 trait impl 尝试接受一组更严格的闭包,这些闭包可以在任何生命周期内接受 MockCtx<'_>

有几种方法可以解决这个问题,但它们都可以解决这个关于闭包参数边界的分歧。

一种方法是显式接受闭包边界中的所有类型。您必须提及 Self 以外的类型。因此,例如,您可以提及具体类型:

trait Push {
    fn push<F: 'static + Fn(&mut MockCtx<'_>)>(&mut self, f: F);
}

See here.

使用特征别名的夜间功能,您可以为所有要求使用一个通用名称:

trait MockFn = 'static + Fn(&mut MockCtx<'_>);
trait Push {
    fn push<F: MockFn>(&mut self, f: F);
}

See here.

使用通用关联类型的夜间功能,您还可以使特征稍微更通用:

trait Push {
    type SelfLike<'a>;
    fn push<F: 'static + Fn(&mut Self::SelfLike<'_>)>(&mut self, f: F);
}

See here.

或者,正如@Jmb 在评论中所说,您可以将闭包作为特征的通用参数:

trait Push<F> {
    fn push(&mut self, f: F);
}

See here.

这是一种相对干净的方法。评论里断言这是没用的,我觉得没用