定义通用特征时出现未知大小编译错误

Unknown size compile error when defining generic trait

作为 Rust 编程的初学者,我对 Size trait 与通用 traits 的结合有点困惑。 我的自定义特征定义了一个方法,它接受两个对象并将它们组合成一个新对象。因为原始对象在组合后过期,方法应该取得参数的所有权。 我还 return 一个带有自定义错误类型的结果,因为组合 可能会失败 并且我想向调用者提供失败的原因。

相关特征是:

pub trait Combinable<T> {
    fn combine(self, other: T) -> CombinationResult<T>;
}
struct CombinationError;

type CombinationResult<T> = std::result::Result<dyn Combinable<T>, CombinationError>

编译此代码时出现以下错误:

error[E0277]: the size for values of type `(dyn Combinable<T> + 'static)` cannot be known at compilation time
    |
7   |     fn combine(self, other: T) -> CombinationResult<T>;
    |                                   ^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time

    |
241 | pub enum Result<T, E> {
    |                 - required by this bound in `std::result::Result`
    |
    = help: the trait `Sized` is not implemented for `(dyn Combinable<T> + 'static)`

我尝试将通用类型参数 T 限制为 Sized(即 pub trait Combinable<T: Sized>),但这没有用。我也在类型别名声明中尝试了同样的方法,也无济于事。

我missing/overlooking是什么东西吗?

dyn Combinable<T> 是一个 unsized 类型(因为特征可以通过任何类型的方式实现,所以值的大小在编译时是未知的)。这反过来意味着 Result<dyn Combinable<T>, ...> 也未调整大小。未确定大小的类型相当有限,其中一个限制是它们不能按值返回。您只能通过某种间接方式(引用、指针等)有意义地处理未确定大小的类型

通常的解决方法是 box 他们:

type CombinationResult<T> = std::result::Result<Box<dyn Combinable<T>>, CombinationError>
                                             // ^^^

dyn Combinable<T>只能用在引用后面(&BoxRc…)。由于您想转让组合项目的所有权,您有两个选择:

  • 最简单的方法是将结果装箱:type CombinationResult<T> = std::result::Result<Box<dyn Combinable<T>>, CombinationError>; 但这会以内存分配和间接的形式增加一些开销。

  • 或者您可以使用通用类型来避免间接寻址:

pub trait Combinable<T> {
    type Combined;
    fn combine(self, other: T) -> CombinationResult<Self::Combined>;
}
pub struct CombinationError;

type CombinationResult<T> = std::result::Result<T, CombinationError>;

Playground

第二种解决方案是将合并的项目移动到 returned 值中。如果没有泛型、特征或错误处理,我们将如何做到这一点:

struct ItemA {}
struct ItemB {}
impl ItemA {
    fn combine (self, other: ItemB) -> CombinedAB {
        CombinedAB { a: self, b: other }
    }
}

struct CombinedAB {
    a: ItemA,
    b: ItemB,
}

Playground

请注意,combine 方法 return 的类型取决于输入的类型。如果我们想将 combine 方法抽象成一个特征,我们不能在声明特征时固定 return 类型,因为我们还不知道将在实现中使用的类型。出于这个原因,我们使用关联类型,这是一种说法:“此类型将由实现指定”。这是使用特征和泛型的相同示例:

pub trait Combinable<T> {
    type Combined;
    fn combine(self, other: T) -> Self::Combined;
}

struct ItemA {}
struct ItemB {}

struct CombinedAB {
    a: ItemA,
    b: ItemB,
}

impl Combinable<ItemB> for ItemA {
    type Combined = CombinedAB;
    fn combine (self, other: ItemB) -> CombinedAB {
        CombinedAB { a: self, b: other }
    }
}

Playground

请注意,我为参数使用了泛型类型,为 returned 值使用了关联类型。 提供了更多详细信息并解释了何时使用其中之一。

当然,如果 combine 方法应该 return 所有输入的相同 具体 类型,那么您可以完全省去关联类型:

struct Combined {}
pub trait Combinable<T> {
    fn combine(self, other: T) -> Combined;
}