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

时间:2016-02-24 03:35:20

标签: rust

请考虑以下代码:

trait Trait<T> {}

fn foo<'a>(_b: Box<Trait<&'a usize>>) {}
fn bar(_b: Box<for<'a> Trait<&'a usize>>) {}

foobar这两个函数似乎都接受Box<Trait<&'a usize>>,但foobar更简洁。他们之间有什么区别?

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

1 个答案:

答案 0 :(得分:65)

for<>语法称为排名较高的特质绑定(HRTB),并且确实是因为闭包而引入的。

简而言之,foobar之间的区别在于foo()内部usize引用的生命周期由来电者提供 函数的>,在{​​{1}}中,函数本身提供相同的生命周期。这种区别对于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的签名(这实际上是关于封锁的生命周期省略如何在Rust中工作):

f

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

您还可以在Nomicon中阅读有关HRTB的其他说明。