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

Geo*_*ard 56 rust

请考虑以下代码:

trait Trait<T> {}

fn foo<'a>(_b: Box<dyn Trait<&'a usize>>) {}
fn bar(_b: Box<dyn for<'a> Trait<&'a usize>>) {}
Run Code Online (Sandbox Code Playgroud)

这两个函数foobar似乎接受Box<Trait<&'a usize>>,虽然foo比更简明做它bar.他们之间有什么区别?

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

Vla*_*eev 81

for<>语法被称为更高级别的特征限制(HRTB),它确实是因为闭包而引入的.

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

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

trait Trait<T> {
    fn do_something(&self, value: T);
}
Run Code Online (Sandbox Code Playgroud)

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

// 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>()));
}
Run Code Online (Sandbox Code Playgroud)

现在,foo()对于这个值可以做什么有一个限制,也就是它可以调用哪些参数do_something().例如,这将无法编译:

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

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

但是,你可以这样做bar:

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

这是有效的,因为现在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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的闭包必须接受引用,T否则就不可能返回选项中包含的值(这与filter()迭代器中的推理相同).

但是应该怎样一生&TFnOnce(&T) -> bool有吗?请记住,我们没有在功能签名中指定生命周期,因为有适当的生命周期; 实际上,编译器为函数签名中的每个引用插入一个生命周期参数.这里应该有些关联的寿命&TFnOnce(&T) -> bool.因此,扩展上述签名的最"明显"方式是:

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

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

另一方面,如果我们扩展这样的签名filter()(这实际上是关于封装的生命周期省略如何在Rust中工作):

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

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

您还可以阅读Nomicon中HRTB的另一种解释.

  • 我认为较高的kinded类型与较高级别的类型正交.更高级别的类型描述了在类型签名中可以发生量化的位置,而更高级别的类型是指在类型构造函数上编写多态代码.另见:http://stackoverflow.com/questions/13317768/kind-vs-rank-in-type-theory (2认同)
  • HRTB 是 Haskell 的“QuantifiedConstraints”的 Rust 名称吗? (2认同)