for<>
语法称为较高等级的特质界限(HRTB),它的引入确实主要是因为关闭。
简而言之,两者之间的区别foo
and bar
是在foo()
内部的寿命usize
提供参考由呼叫者的函数,而在bar()
提供相同的生命周期由函数本身。这种区别对于实施非常重要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
可以选择所需的生命周期而不是调用者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()
在迭代器上)。
但一生应该怎样&T
in FnOnce(&T) -> bool
有?请记住,我们在函数签名中指定生命周期不仅仅是因为存在生命周期省略;而是因为存在生命周期省略。实际上,编译器会为函数签名内的每个引用插入一个生命周期参数。那里should be some与相关的生命周期&T
in FnOnce(&T) -> bool
。因此,扩展上面签名的最“明显”的方法是:
fn filter<'a, F>(self, f: F) -> Option<T> where F: FnOnce(&'a T) -> bool
然而,这是行不通的。正如示例中所示Trait
以上,一生'a
is 严格更长比该函数中任何局部变量的生命周期长,包括value
在 match 语句中。因此无法申请f
to &value
因为一生的不匹配。使用这样的签名编写的上述函数将无法编译。
另一方面,如果我们扩展签名filter()
像这样(这实际上是 Rust 中闭包生命周期省略的工作原理):
fn filter<F>(self, f: F) -> Option<T> where F: for<'a> FnOnce(&'a T) -> bool
然后打电话f
with &value
作为一个论点是完全有效的:we现在可以选择生命周期,因此使用局部变量的生命周期绝对没问题。这就是 HRTB 如此重要的原因:没有它们,您将无法表达许多有用的模式。
您还可以阅读 HRTB 的另一个解释:Nomicon https://doc.rust-lang.org/nightly/nomicon/hrtb.html.