for <>语法与常规生存期绑定有何不同?


75

考虑以下代码:

trait Trait<T> {}

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

这两个函数foobar似乎都接受a Box<Trait<&'a usize>>,尽管foo它比bar。它们之间有什么区别?

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

Answers:


107

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

总之,之间的差foobar是,在foo()寿命为内部usize参考设置由呼叫者的功能,而在bar()相同的寿命,提供由函数本身。这种区别对于foo/的实现非常重要bar

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

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

请记住,生存期参数与通用类型参数非常相似。使用泛型函数时,始终指定其所有类型参数,并提供具体类型,然后编译器使函数单一化。同样的事情会终身参数:当你调用哪个有一生的时间参数的函数,指定的一生,虽然含蓄:

// 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()。例如,它将不会编译:

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

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

但是,您可以执行以下操作bar

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

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

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

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()迭代器上的推理相同)。

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

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

但是,这行不通。如结合的例子Trait以上,寿命'a严格长比任何本地变量的生存期在这个函数中,包括value匹配语句内。因此,不可能适用f&value,因为一辈子不匹配。用这种签名编写的上述函数将无法编译。

另一方面,如果我们filter()像这样扩展签名(实际上,这就是现在在Rust中对闭包进行生存期删除的方式):

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

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

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


太棒了!这真的很清楚,谢谢。希望这将是该问题的规范答案。作为后续(如果有链接,那会很好,因为这超出了我最初的问题的范围):为什么它们具有较高的性状界限?它们与更高类型的类型(我有点熟悉)有什么关系吗?
乔治·希利亚德'02

1
@thirtythreeforty是的,我相信它们的名称恰好在HKT之后,因为它们确实很像。我可以想象一下for,如果可用,HKT也可以用编写:for<T> Monad<T>,或者至少它们具有类似的概念-指定无限数量的特征范围(或类型,对于HKT而言),并用某些参数(生命周期或类型)进行参数化)。就是说,HRTB可以支持类型以及生命周期,但这还没有人想到一个具体的设计。
弗拉基米尔·马特维耶夫(Fladimir Matveev)

2
我认为种类较高的类型与等级较高的类型正交。较高等级的类型描述类型签名中可能发生量化的位置,较高种类的类型表示通过类型构造函数编写多态代码。另请参阅:stackoverflow.com/questions/13317768/…–
BurntSushi5

1
@ BurntSushi5哦,好吧,我总是把它们彼此混淆:(
弗拉基米尔·

HRTB是Haskell的Rust名称QuantifiedConstraints吗?
dspyz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.