考虑以下代码:
trait Trait<T> {}
fn foo<'a>(_b: Box<dyn Trait<&'a usize>>) {}
fn bar(_b: Box<dyn for<'a> Trait<&'a usize>>) {}
两个函数foo
和 bar
似乎接受 Box<Trait<&'a usize>>
, 尽管 foo
比 bar
更简洁吗.它们有什么区别?
此外,在什么情况下我需要for<>
像上面那样的语法?我知道 Rust 标准库在内部使用它(通常与闭包相关),但为什么我的代码可能需要它?
最佳答案
for<>
语法称为higher-ranked trait bound (HRTB),它的引入确实主要是因为闭包。
简而言之,foo
之间的区别和 bar
是在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()
的推理相同)。
但是生命周期应该是多少&T
在FnOnce(&T) -> bool
有?请记住,我们不在函数签名中指定生命周期只是因为存在生命周期省略;实际上,编译器会为函数签名中的每个引用插入一个生命周期参数。 应该一些生命周期与&T
相关联在FnOnce(&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 的另一种解释。 .
关于rust - "for<>"语法与常规生命周期界限有何不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35592750/