请考虑以下代码:
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)
这两个函数foo和bar似乎接受Box<Trait<&'a usize>>,虽然foo比更简明做它bar.他们之间有什么区别?
另外,在什么情况下我需要for<>像上面那样的语法?我知道Rust标准库在内部使用它(通常与闭包有关),但为什么我的代码需要它呢?
Vla*_*eev 81
for<>语法被称为更高级别的特征限制(HRTB),它确实是因为闭包而引入的.
总之,之间的差foo和bar是,在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()迭代器中的推理相同).
但是应该怎样一生&T中FnOnce(&T) -> bool有吗?请记住,我们没有在功能签名中指定生命周期,因为有适当的生命周期; 实际上,编译器为函数签名中的每个引用插入一个生命周期参数.这里应该是有些关联的寿命&T在FnOnce(&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的另一种解释.
| 归档时间: |
|
| 查看次数: |
4267 次 |
| 最近记录: |