关于rust:for <>语法与常规生命周期限制有何不同?

How does for<> syntax differ from a regular lifetime bound?

考虑以下代码:

1
2
3
4
trait Trait< T > {}

fn foo<'a>(_b: Box<dyn Trait<&'a usize>>) {}
fn bar(_b: Box<dyn for<'a> Trait<&'a usize>>) {}

函数foobar似乎都接受Box>,尽管foobar更为简洁。 它们之间有什么区别?

另外,在什么情况下我需要上述的for<>语法? 我知道Rust标准库在内部使用它(通常与闭包相关),但是为什么我的代码需要它?


for<>语法称为高等级特征绑定(HRTB),实际上引入它的主要原因是闭包。

简而言之,foobar之间的区别在于,在foo()中,内部usize引用的生存期由函数的调用者提供,而在bar()中,相同的生存期由函数的调用者提供。本身。并且此区别对于foo / bar的实现非常重要。

但是,在这种特殊情况下,当Trait没有使用类型参数的方法时,这种区别是没有意义的,因此让我们假设Trait看起来像这样:

1
2
3
trait Trait< T > {
    fn do_something(&self, value: T);
}

请记住,生存期参数与通用类型参数非常相似。使用泛型函数时,始终指定其所有类型参数,并提供具体类型,然后编译器使函数单一化。生命周期参数也是如此:当调用具有生命周期参数的函数时,尽管隐式指定了生命周期,但也可以:

1
2
3
4
5
6
7
// imaginary explicit syntax
// also assume that there is TraitImpl::new::< T >() -> TraitImpl< T >,
// and TraitImpl< T >: Trait< T >

'a: {
    foo::<'a>(Box::new(TraitImpl::new::<&'a usize>()));
}

现在,foo()可以使用该值进行限制,即可以使用do_something()调用哪些参数。例如,它将不会编译:

1
2
3
4
fn foo<'a>(b: Box<Trait<&'a usize>>) {
    let x: usize = 10;
    b.do_something(&x);
}

这不会编译,因为局部变量的生命周期严格小于生命周期参数指定的生命周期(我清楚这是为什么),因此您无法调用b.do_something(&x),因为它要求其参数具有生命周期'a,严格大于x

但是,您可以使用bar执行此操作:

1
2
3
4
fn bar(b: Box<for<'a> Trait<&'a usize>>) {
    let x: usize = 10;
    b.do_something(&x);
}

之所以有效,是因为现在bar可以选择所需的生存期,而不是bar的调用者。

当您使用接受引用的闭包时,这确实很重要。例如,假设您要在Option< T >上编写一个filter()方法:

1
2
3
4
5
6
7
8
impl< T > Option< T > {
    fn filter<F>(self, f: F) -> Option< T > where F: FnOnce(&T) -> bool {
        match self {
            Some(value) => if f(&value) { Some(value) } else { None }
            None => None
        }
    }
}

这里的闭包必须接受对T的引用,因为否则将不可能返回该选项中包含的值(这与迭代器上filter()的原因相同)。

但是FnOnce(&T) -> bool中的&T应该具有什么寿命?请记住,我们不仅仅在函数签名中指定生存期,因为存在生存期省略。实际上,编译器会在函数签名内为每个引用插入生命周期参数。 FnOnce(&T) -> bool中的&T应该有一些寿命。因此,扩展上述签名的最"明显"的方式是:

1
fn filter<'a, F>(self, f: F) -> Option< T > where F: FnOnce(&'a T) -> bool

但是,这行不通。与上面的Trait的示例一样,生存期'a严格大于此函数中任何局部变量的生存期,包括match语句内的value。因此,由于生命周期不匹配,无法将f应用于&value。用这种签名编写的上述函数将无法编译。

另一方面,如果我们像这样扩展filter()的签名(这实际上是Rust闭包的生存期省略现在的工作方式):

1
fn filter<F>(self, f: F) -> Option< T > where F: for<'a> FnOnce(&'a T) -> bool

然后使用&value作为参数调用f是完全有效的:我们现在可以选择生存期,因此使用局部变量的生存期绝对可以。这就是HRTB非常重要的原因:没有它们,您将无法表达很多有用的模式。

您还可以在Nomicon中阅读有关HRTB的另一种解释。