【问题标题】:"Expected trait A, found &A" when trying to box a trait object尝试装箱特征对象时出现“预期的特征 A,找到 &A”
【发布时间】:2018-09-16 10:16:27
【问题描述】:

我正在尝试创建一个 trait,它既可以检索(并返回对)另一个 trait 的 trait 对象的引用,也可以创建一个(并返回它的盒装版本),将选择权留给实现者(其中意味着我需要将返回对象的生命周期限制为生产者的生命周期)。但是,我遇到了错误:

use std::borrow::Borrow;
use std::collections::HashMap;

trait A { 
    fn foobar(&self) {
        println!("!"); 
    } 
}

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>>;
}

impl<'b, B: Borrow<A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>> {
        self.get(name).map(|borrow| Box::new(borrow.borrow()))
    }
}

错误是:

error[E0308]: mismatched types
  --> src/main.rs:20:9
   |
20 |         self.get(name).map(|borrow| Box::new(borrow.borrow()))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait A, found &A
   |
   = note: expected type `std::option::Option<std::boxed::Box<dyn A + 'a>>`
              found type `std::option::Option<std::boxed::Box<&dyn A>>`

这让我感到困惑,因为我希望 &amp;A 也是 A。我试过impl&lt;'a&gt; A for &amp;'a A,但这也无济于事。有没有办法解决这个问题?

【问题讨论】:

  • A 实现一个 trait 并不意味着 &amp;A 可以。由于 Rust 的 auto-ref 和 auto-deref 行为,有时看起来确实如此。例如,它将自动取消引用一个变量,因此您可以键入 foo.bar() 而不是 (*foo).bar()。
  • 为什么还要返回Box&lt;&amp;dyn A&gt;?装箱参考似乎不是很有用。
  • 我注意到name: &amp;'a str 在这里受到了不必要的限制,因为返回的引用没有绑定到name。也许你有ProducerOrContainer 的其他实现,其中返回值是从name 派生的,但如果没有,摆脱'a 会更灵活。

标签: rust trait-objects


【解决方案1】:

...可以检索(并返回对)另一个特征的特征对象的引用,或者创建一个(并返回它的盒装版本)。

有了这个要求,Box 将不起作用。 Box拥有它的数据,但有时你借用了数据,你不能移动。

在标准库中有一个名为Cow 的类型,它是对值是借用还是拥有的抽象。但是,这里可能不太适合您,因为它不会让您以 Box 的身份拥有数据,并且还要求您的数据类型必须实现 ToOwned。

但我们可以将您的要求直接建模为enum:

enum BoxOrBorrow<'a, T: 'a + ?Sized> {
    Boxed(Box<T>),
    Borrowed(&'a T),
}

并通过实现Deref 使其符合人体工程学:

use std::ops::Deref;

impl<'a, T> Deref for BoxOrBorrow<'a, T> {
    type Target = T;
    fn deref(&self) -> &T {
        match self {
            BoxOrBorrow::Boxed(b) => &b,
            BoxOrBorrow::Borrowed(b) => &b,
        }
    }
}

这使您可以将自定义 BoxOrBorrow 类型视为任何其他引用 - 您可以使用 * 取消引用它或将其传递给任何期望引用 T 的函数。

这就是您的代码的样子:

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>>;
}

impl<'b, B: Borrow<dyn A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>> {
        self.get(name)
            .map(|b| BoxOrBorrow::Borrowed(b.borrow()))
    }
}

【讨论】:

    【解决方案2】:

    您可以通过为&amp;'_ dyn A 实现A 并添加显式转换来编译原始代码:

    self.get(name).map(|borrow| Box::new(borrow.borrow()) as Box<dyn A>)
    

    闭包不是coercion site。编译器查看闭包的内容以查看返回值是什么,并得出结论它返回Box&lt;&amp;'a dyn A&gt;。但是闭包本身不能从“函数返回Box&lt;&amp;'a dyn A&gt;”强制转换为“函数返回Box&lt;dyn A + 'a&gt;”,因为这些类型在结构上是不同的。您添加强制转换以告诉编译器您希望闭包首先返回 Box&lt;dyn A&gt;。

    但这有点傻。 Box 引用在这里完全没有必要,将其转换为 Box&lt;dyn A&gt; 只是为调用者增加了另一层间接性。正如Peter Hall's answer 所描述的那样,返回一个封装“要么盒装特征对象,或对特征对象的引用”概念的类型会更好。


    在带有通用关联类型(“GAT”)的 Rust 的未来版本中,可以将返回类型设为 ProducerOrContainer 的关联类型,如下所示:

    trait ProducerOrContainer {
        type Result<'a>: A;
        fn get_a<'a>(&'a self, name: &'a str) -> Option<Result<'a>>;
    }
    

    使用这个 trait 定义,每个实现 ProducerOrContainer 的类型都可以选择它返回的类型,因此您可以为某些 impls 选择 Box&lt;dyn A&gt;,为其他类型选择 &amp;'a dyn A。但是,这在当前的 Rust (1.29) 中是不可能的。

    【讨论】:

      猜你喜欢
      • 2015-07-15
      • 2022-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-27
      • 1970-01-01
      相关资源
      最近更新 更多