如何为<>语法不同于常规的生命周期界限? [英] How does for<> syntax differ from a regular lifetime bound?

查看:14
本文介绍了如何为<>语法不同于常规的生命周期界限?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下代码:

trait Trait<T> {}

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

foobar 两个函数似乎都接受一个 Box>,尽管 foobar 更简洁.它们有什么区别?

Both functions foo and bar seem to accept a Box<Trait<&'a usize>>, although foo does it more concisely than bar. What is the difference between them?

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

Additionally, in what situations would I need for<> syntax like that above? I know the Rust standard library uses it internally (often related to closures), but why might my code need it?

推荐答案

for<> 语法被称为 higher-ranked trait bound (HRTB),它是确实主要是因为闭包而引入的.

for<> syntax is called higher-ranked trait bound (HRTB), and it was indeed introduced mostly because of closures.

简而言之,foobar 的区别在于 foo() 内部 usize 的生命周期code> 引用由函数的调用者提供,而在bar()中,相同的生命周期由函数本身提供.而这个区别对于foo/bar的实现非常重要.

In short, the difference between foo and bar is that in foo() the lifetime for the internal usize reference is provided by the caller of the function, while in bar() the same lifetime is provided by the function itself. And this distinction is very important for the implementation of foo/bar.

然而,在这种特殊情况下,当 Trait 没有使用类型参数的方法时,这种区分是没有意义的,所以让我们想象一下 Trait 看起来像这样:

However, in this particular case, when Trait has no methods which use the type parameter, this distinction is pointless, so let's imagine that Trait looks like this:

trait Trait<T> {
    fn do_something(&self, value: T);
}

请记住,生命周期参数与泛型类型参数非常相似.当你使用泛型函数时,你总是指定它的所有类型参数,提供具体的类型,并且编译器对函数进行单态化.生命周期参数也是一样:当你调用一个具有生命周期参数的函数时,指定生命周期,尽管是隐式的:

Remember, lifetime parameters are very similar to generic type parameters. When you use a generic function, you always specify all of its type parameters, providing concrete types, and the compiler monomorphizes the function. Same thing goes with lifetime parameters: when you call a function which have a lifetime parameter, you specify the lifetime, albeit implicitly:

// 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().例如,这不会编译:

And now there is a restriction on what foo() can do with this value, that is, with which arguments it may call do_something(). For example, this won't compile:

fn foo<'a>(b: Box<Trait<&'a usize>>) {
    let x: usize = 10;
    b.do_something(&x);
}

这不会编译,因为局部变量的生命周期严格小于生命周期参数指定的生命周期(我认为很清楚为什么会这样),因此你不能调用 b.do_something(&x) 因为它要求它的参数具有生命周期 'a,它严格大于 x 的生命周期.

This won't compile because local variables have lifetimes which are strictly smaller than lifetimes specified by the lifetime parameters (I think it is clear why it is so), therefore you can't call b.do_something(&x) because it requires its argument to have lifetime 'a, which is strictly greater than that of x.

但是,您可以使用 bar 来做到这一点:

However, you can do this with bar:

fn bar(b: Box<for<'a> Trait<&'a usize>>) {
    let x: usize = 10;
    b.do_something(&x);
}

这是可行的,因为现在 bar 可以选择所需的生命周期而不是 bar 的调用者.

This works because now bar can select the needed lifetime instead of the caller of bar.

当您使用接受引用的闭包时,这很重要.例如,假设你想在 Option 上写一个 filter() 方法:

This does matter when you use closures which accept references. For example, suppose you want to write a filter() method on 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() 在迭代器上).

The closure here must accept a reference to T because otherwise it would be impossible to return the value contained in the option (this is the same reasoning as with filter() on iterators).

但是 &TFnOnce(&T) -> 中应该是什么生命周期?bool 有吗?请记住,我们不会仅仅因为存在生命周期省略而在函数签名中指定生命周期;实际上,编译器为函数签名中的每个引用插入了一个生命周期参数.在 FnOnce(&T) -> 中,应该一些&T 相关联的生命周期.布尔.因此,扩展上述签名的最明显"方法是:

But what lifetime should &T in FnOnce(&T) -> bool have? Remember, we don't specify lifetimes in function signatures only because there is lifetime elision in place; actually the compiler inserts a lifetime parameter for each reference inside a function signature. There should be some lifetime associated with &T in FnOnce(&T) -> bool. So, the most "obvious" way to expand the signature above would be this:

fn filter<'a, F>(self, f: F) -> Option<T> where F: FnOnce(&'a T) -> bool

然而,这行不通.与上面 Trait 的例子一样,生命周期 'a 严格比这个函数中任何局部变量的生命周期长,包括 匹配语句中的值.因此,由于生命周期不匹配,不可能将 f 应用于 &value.上面用这种签名编写的函数将无法编译.

However, this is not going to work. As in the example with Trait above, lifetime 'a is strictly longer than the lifetime of any local variable in this function, including value inside the match statement. Therefore, it is not possible to apply f to &value because of lifetime mismatch. The above function written with such signature won't compile.

另一方面,如果我们像这样扩展 filter() 的签名(这实际上是现在 Rust 中闭包的生命周期省略的工作方式):

On the other hand, if we expand the signature of filter() like this (and this is actually how lifetime elision for closures works in Rust now):

fn filter<F>(self, f: F) -> Option<T> where F: for<'a> FnOnce(&'a T) -> bool

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

then calling f with &value as an argument is perfectly valid: we can choose the lifetime now, so using the lifetime of a local variable is absolutely fine. And that's why HRTBs are important: you won't be able to express a lot of useful patterns without them.

您还可以在 Nomicon 中阅读对 HRTB 的另一种解释.

You can also read another explanation of HRTBs in Nomicon.

这篇关于如何为&lt;&gt;语法不同于常规的生命周期界限?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆