首页 > 解决方案 > 使用具有泛型的更高级别的特征边界

问题描述

我偶然发现了一个有趣的边缘案例:使用排名更高的生命周期边界来接受返回泛型参数的闭包,例如for<'a> FnOnce(&'a T) -> R: MyTrait. 没有办法指定R生命最多'a。也许最好用一个例子来解释。

让我们定义一个简单的类引用类型包装一个值:

struct Source;

struct Ref<'a> {
    source: &'a Source,
    value: i32,
}

为方便起见,让我们添加一个辅助构造函数。在这里,我将使用显式生命周期来使借用不言自明:

impl Source {
    fn new_ref<'a>(&'a self, value: i32) -> Ref<'a> {
        Ref { source: self, value }
    }
}

这是一个非常奇特的整数复制例程实现,使用 HRTB 并在我们的Ref:

fn call_1<F>(callback: F) -> i32
where
    for<'a> F: FnOnce(&'a Source) -> Ref<'a>,
{
    let source = Source;
    callback(&source).value
}

fn fancy_copy_1(value: i32) -> i32 {
    call_1(|s| s.new_ref(value))
}

这很好,并且按预期工作。我们知道它Ref不会超过Source并且编译器也能够接受它。现在让我们创建一个简单的 trait 并实现它以供我们参考:

trait MyTrait {
    fn value(&self) -> i32;
}

impl<'a> MyTrait for Ref<'a> {
    fn value(&self) -> i32 {
        self.value
    }
}

并修改我们的整数复制例程以返回实现该特征的泛型类型,而不仅仅是返回Ref

fn call_2<R, F>(callback: F) -> i32
where
    for<'a> F: FnOnce(&'a Source) -> R,
    R: MyTrait,
{
    let source = Source;
    callback(&source).value()
}

fn fancy_copy_2(value: i32) -> i32 {
    call_2(|s| s.new_ref(value))
}

突然我得到一个错误:cannot infer an appropriate lifetime for autoref due to conflicting requirements. 为了方便起见,Rust 操场链接。从某些角度来看,这实际上是有道理的:与Ref<'a>第一个示例不同,我从未说过R最多只能活到'a. 很可能活得更久,因此可以访问释放的内存。所以我需要用它自己的生命周期来注释它。但是没有地方可以做!第一个本能是把生命限制在界限内:

where
    for<'a> F: FnOnce(&'a Source) -> R,
    R: MyTrait + 'a,

这当然是不正确的,因为'a只为第一个界限定义。

这就是我感到困惑的地方,开始搜索,但从未发现任何关于将 HRTB 和泛型类型结合在一起的信息。也许在 Rust 中更有经验的人有什么建议?

更新 1。

当我更多地考虑这个问题时,我记得我可以使用impl Trait语法。这看起来像我的问题的解决方案:

fn call_3<F>(callback: F) -> i32
where
     F: for<'a> FnOnce(&'a Source) -> (impl MyTrait + 'a),
{
    let source = Source;
    callback(&source).value()
}

fn fancy_copy_3(value: i32) -> i32 {
    call_3(|s| Box::new(s.new_ref(value)))
}

然而,这不起作用,因为由于impl MyTrait某种原因(可能是暂时的)不允许在这个地方。但这让我想到了dyn Trait语法,而且确实有效!

fn call_4<F>(callback: F) -> i32
where
     F: for<'a> FnOnce(&'a Source) -> Box<dyn MyTrait + 'a>,
{
    let source = Source;
    let value = callback(&source); // note how a temporary is required
    value.value()
}

fn fancy_copy_4(value: i32) -> i32 {
    call_4(|s| Box::new(s.new_ref(value)))
}

这是我的解决方案:使用 dyn Trait语法有一个地方可以放置+ 'a! 不幸的是,这个解决方案对我来说仍然不太适用,因为它需要 trait 上的对象安全性,并且增加了分配装箱值的开销。但至少它是一些东西。

标签: rustlifetime

解决方案


推荐阅读