for<>
语法被称为higher-ranked trait bound(HRTB),它的引入实际上主要是因为闭包.
简言之,foo
和bar
之间的区别在于,在foo()
中,内部usize
参考的生命周期 被提供给函数的by the caller,而在bar()
中,相同的生命周期 被提供给函数的by the function itself.这种区别对于foo
/bar
的实施非常重要.
然而,在这种特殊情况下,当Trait
没有使用类型参数的方法时,这种区别是没有意义的,所以让我们想象Trait
看起来像这样:
trait Trait<T> {
fn do_something(&self, value: T);
}
请记住,生存期参数与泛型类型参数非常相似.当您使用泛型函数时,您总是指定它的所有类型参数,提供具体的类型,并且编译器将函数单态化.生命周期参数也是一样:当你调用一个有生命周期参数的函数时,you指定生命周期,尽管是隐式的:
// 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
可以 Select 所需的生存期,而不是bar
的调用者.
当使用接受引用的闭包时,这一点很重要.例如,假设您想在Option<T>
上编写一个filter()
方法:
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
人应该有什么样的生命周期 呢?记住,我们在函数签名中指定生命周期并不是因为存在生命周期省略;实际上,编译器在函数签名中 for each 引用插入一个生存期参数.FnOnce(&T) -> bool
中有should个生命周期 与&T
个相关.因此,扩展上述签名的最"明显"的方法是:
fn filter<'a, F>(self, f: F) -> Option<T> where F: FnOnce(&'a T) -> bool
然而,这是行不通的.与上面Trait
的例子一样,生存期'a
比该函数中任何局部变量的生存期都长strictly longer,包括match语句中的value
.因此,由于生命周期 不匹配,不可能应用f
到&value
.用这种签名编写的上述函数将无法编译.
另一方面,如果我们像这样扩展filter()
的签名(这实际上是终生省略在Rust now中的工作原理):
fn filter<F>(self, f: F) -> Option<T> where F: for<'a> FnOnce(&'a T) -> bool
然后用&value
作为参数调用f
是完全有效的:we现在可以 Select 生存期,所以使用局部变量的生存期是绝对正确的.这就是为什么HRTBs很重要:如果没有它们,你将无法表达很多有用的模式.
你也可以在Nomicon页阅读关于HRTBs的另一个解释.