E0038

未匹配的标注

诸如 Box<Trait> 这类的 Trait 对象只能在当 trait 满足特定要求时被构造。

特性对象是动态分配的一种形式,内部类型使用动态大小的类型。因此,对于给定的 trait Trait,当 Trait 被作为类型处理时, 在 Box<Trait> 中, 内部的类型为 ‘unsized’。 在这种情况下, box 指针是一个 “胖指针”,包含一个额外的指针,该指针指向动态分配方法的表。此设计对允许在 trait 对象中使用的 trait 类型施加了一些限制,这些限制统称为 “对象安全” 规则。

尝试为非对象安全的 trait 创建 trait 对象将会触发此错误。

有以下这几种规则:

trait 不能被限定为 Self: Sized

Trait 被作为类型时, 该类型不会实现 Sized trait,因为类型在编译时并不知道大小,并且只能用指针进行访问。因此,如果我们具有以下 trait :

trait Foo where Self: Sized {

}

我们会无法创建一个 Box<Foo>&Foo 类型的对象,因为在这种情况下, Self 不是 Sized

通常, Self: Sized 用于表明 trait 不应该被用作 trait 对象。 如果该 trait 来自于你自己的 crate ,请考虑此限制。

方法在其参数或返回类型中引用Self类型

当 trait 具有如下方法时:

trait Trait {
    fn foo(&self) -> Self;
}

impl Trait for String {
    fn foo(&self) -> Self {
        "hi".to_owned()
    }
}

impl Trait for u8 {
    fn foo(&self) -> Self {
        1
    }
}

(注意 &self&mut self (作为返回值)都是可以的, 除了 Self 类型会导致这种问题)

在这种情况下,编译器不能在如下情况预测 foo() 的返回值:

trait Trait {
    fn foo(&self) -> Self;
}

fn call_foo(x: Box<Trait>) {
    let y = x.foo(); // What type is y?
    // ...
}

如果只有一些方法不是对象安全的,可以在其上添加一个 where Self: Sized 来标记其为明确不可用于 trait 对象。 在所有其它的实现中(这些)函数是依然可用的,包括 Box<Trait> 这个 自身是 sized 的类型 (假设已经 impl Trait for Box<Trait>)。

trait Trait {
    fn foo(&self) -> Self where Self: Sized;
    // 其它函数
}

现在, foo() 再也不能在一个 trait 对象上调用了, 但是你现在允许生成一个 trait 对象,可以调用其内任何一个对象安全的方法。在这种约束下,依然可以在实现了这个 trait 的非 trait 对象类型上调用 foo() ( With such a bound, one can still call foo() on types implementing that trait that aren’t behind trait objects. )。

方法具有泛型类型参数

前面提到, trait 对象包含指向方法表的指针。所以,如果我们有如下代码:

trait Trait {
    fn foo(&self);
}

impl Trait for String {
    fn foo(&self) {
        // 实现 1
    }
}

impl Trait for u8 {
    fn foo(&self) {
        // 实现 2
    }
}
// ...

在编译时, Trait 的每个实现都会生成一个包含与实现相关的各种方法(和其他项)的表。

这可以正常工作,但是当该方法获取泛型参数时,我们可能会遇到问题。

通常,泛型函数会被”单态化”。例如,如果有:

fn foo<T>(x: T) {
    // ...
}

foo::<u8>()foo::<bool>()foo::<String>(), 或其它任何类型替换成的机器代码都是不同的。因此编译器是按需生成代码。如果用 bool 类型参数调用 foo() ,编译器就只会生成 foo::<bool>() 的代码。当我们有其它类型参数的时候,编译器生成的单态化实现的数量不会剧增,因为只有在使用未经参数化替换的函数调用时编译器才会生成一个实现(即,如果只是声明而不使用,替换不会实现,也就不会单态化)。

然而,对 trait objects 我们必须生成一个包含每个实现 trait 的对象的表。如果有类型参数,我们需要对每个实现了 trait 的类型添加实现,理论上可以有无限多个类型。we have to make a table containing every object that implements the trait. Now, if it has type parameters, we need to add implementations for every type that implements the trait, and there could theoretically be an infinite number of types.

例如,有:

trait Trait {
    fn foo<T>(&self, on: T);
    // 其它方法
}

impl Trait for String {
    fn foo<T>(&self, on: T) {
        // 实现1
    }
}

impl Trait for u8 {
    fn foo<T>(&self, on: T) {
        // 实现2
    }
}

// 8个更多实现

现在,如果我们有如下代码:


fn call_foo(thing: Box<Trait>) {
    thing.foo(true); // 这可能是以上8种类型中的任何一种
    thing.foo(1);
    thing.foo("hello");
}

我们不仅需要为所有Trait 的方法实现创建一个表,还需要为反馈给foo() 的每种不同类型创建一个这样的表。在这种情况下,结果是 (Trait的 10种类型实现)*(提供给 foo() 的三种类型) = 三十种实现!

对于真实世界的 trait ,这些数字可能会剧增。

要解决此问题,如果不打算使用类型参数调用该方法,建议使用一个 where Self: Sized 绑定,类似于上面针对子错误的修复方法。

trait Trait {
    fn foo<T>(&self, on: T) where Self: Sized;
    // 其它方法
}

如果不想要这种选择,可以考虑将 type 参数替换为另一个 trait object (例如,如果是 T: OtherTrait , 使用 on: Box<OtherTrait>)。 如果给此方法的类型数量是有限的,请考虑手动列出不同类型的方法。

方法没有接收者

不能调用不带有 self 参数的方法,因为没有办法为它们获取指向方法表的指针。

trait Foo {
    fn foo() -> u8;
}

这可以调用为 <Foo as Foo>::foo(), 无法选择实现。

添加 Self: Sized 绑定到这些方法通常会进行编译

trait Foo {
    fn foo() -> u8 where Self: Sized;
}

trait 不能包含关联常量

就像静态函数,关联常量是不会存储在方法表里面的。如果 trait 或者任何子 trait 包含关联常数,它们就不能用来制成对象。


trait Foo {
    const X: i32;
}

impl Foo {}

一个简单的解决办法是改用辅助方法:

trait Foo {
    fn x(&self) -> i32;
}

在 super trait 列表中,trait 不能使用 Self 作为类型参数

这类似于第二个子错误,但是更巧妙。它发生在以下情况:


trait Super<A: ?Sized> {}

trait Trait: Super<Self> {
}

struct Foo;

impl Super<Foo> for Foo{}

impl Trait for Foo {}

fn main() {
    let x: Box<dyn Trait>;
}

现在, super trait 可能具有以下方法:

trait Super<A: ?Sized> {
    fn get_a(&self) -> &A; // 注意,这是对象安全的!
}

如果 Trait trait 是源自诸如 Super<String>Super<T> 之类的trait (其中 Foo 本身是 Foo<T>), 这没关系,因为给定一个类型 get_a() 必然会返回该类型的对象。

然而,如果它源自 Super<Self>, 即使 Super 是对象安全的, get_a() 方法将会在调用时返回一个未知类型的对象。 Self 类型参数使我们对象安全的 trait 不再安全,因此在指定 super trait 的时候将会禁用它。

对此没有简单的解决方法,通常需要重构代码,以便不再需要从Super<Self> 派生。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~