考虑下面的代码:

trait Trait<T> {}

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

函数foobar似乎都接受Box<Trait<&'a usize>>,尽管foobar更简洁.它们之间有什么区别?

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

推荐答案

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

简言之,foobar之间的区别在于,在foo()中,内部usize参考的生命周期 被提供给函数的by the caller,而在bar()中,相同的生命周期 被提供给函数的by the function itself.这种区别对于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可以 Select 所需的生存期,而不是bar的调用者.

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

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()的推理相同).

但是FnOnce(&T) -> bool人中的&T人应该有什么样的生命周期 呢?记住,我们在函数签名中指定生命周期并不是因为存在生命周期省略;实际上,编译器在函数签名中 for each 引用插入一个生存期参数.FnOnce(&T) -> bool中有should个生命周期 与&T个相关.因此,扩展上述签名的最"明显"的方法是:

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

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

另一方面,如果我们像这样扩展filter()的签名(这实际上是终生省略在Rust now中的工作原理):

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

然后用&value作为参数调用f是完全有效的:we现在可以 Select 生存期,所以使用局部变量的生存期是绝对正确的.这就是为什么HRTBs很重要:如果没有它们,你将无法表达很多有用的模式.

你也可以在Nomicon页阅读关于HRTBs的另一个解释.

Rust相关问答推荐

捕获Rust因C++异常而产生panic

如果A == B,则将Rc A下推到Rc B

如何将`Join_all``Vec<;Result<;Vec<;Foo&>;,Anywhere::Error&>;`合并到`Result<;Vec<;Foo&>;,Anywhere::Error&>;`

这种获取-释放关系是如何运作的?

如何在 struct 的自定义序列化程序中使用serde序列化_WITH

为什么Rust不支持带关联常量的特征对象?

在使用粗粒度锁访问的数据 struct 中使用 RefCell 是否安全?

提取指向特征函数的原始指针

Rust FFI 和 CUDA C 性能差异

使用在功能标志后面导入的类型,即使未启用功能标志

如何在 Rust 中将枚举变体转换为 u8?

打印 `format_args!` 时borrow 时临时值丢失

使用自定义 struct 收集 Vec

更好的方法来模式匹配带有 Rust 的窥视孔装配说明窗口?

无法把握借来的价值不够长寿,请解释

在 Rust 中枚举字符串的最佳方式? (字符()与 as_bytes())

如何制作具有关联类型的特征的类型擦除版本?

Rust 为什么 (u32, u32) 的枚举变体的大小小于 (u64)?

Rust 中的运行时插件

为什么我不能将元素写入 Rust 数组中移动的位置,但我可以在元组中完成