提供将 &self 转换为特征对象的方法

Provided method casting &self to trait object

我有一个特征,我想提供一种方法。该方法将根据一些助手来实现,这些助手在特征内部没有任何业务,并且非常重要,动态多态性比使它们通用更有意义。所以我的代码是

fn use_trait(x: &Trait) {
    println!("object says {}", x.needed());
}

trait Trait {
    fn needed(&self) -> &str;

    fn provided(&self) {
        use_trait(self);
    }
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }
}

fn main() {
    Struct().provided();
}

然而,does not compile,错误:

error[E0277]: the trait bound `Self: std::marker::Sized` is not satisfied
 --> <anon>:9:19
  |
9 |         use_trait(self);
  |                   ^^^^ the trait `std::marker::Sized` is not implemented for `Self`
  |
  = help: consider adding a `where Self: std::marker::Sized` bound
  = note: required for the cast to the object type `Trait`

我理解为什么 - 不能保证有人不会为未确定大小的类型实现特征(从 &T where T: Trait 转换为 &Trait 需要 T: Sized,但声明不要求)。

但是,建议不会满足我的需要。我可以加

fn needed(&self) -> &str where Self: Sized

但是 needed() 方法 无法在 &Trait 上访问(因为 Trait : ?Sized),这使它变得无用,因为类型(实际有用的类型)总是 处理为 Arc<Trait>。并添加

trait Trait: Sized

更糟,因为它根本不允许 &TraitTrait 作为一个类型没有大小,所以 Trait 类型 not 实现特征 Trait).

当然我可以简单的

fn use_trait<T: Trait>(x: &T)

但是在实际代码中它背后有很多东西,所以我不希望在那里进行单态化,特别是因为 trait 否则总是作为 trait 对象处理。

有没有什么方法可以告诉 Rust impl Trait 的所有类型都必须调整大小,这里是一个应该适用于所有类型的方法的定义?

您需要在 Trait 及其实现上添加一个 as_trait 函数:

trait Trait {
    fn needed(&self) -> &str;

    fn provided(&self) {
        use_trait(self.as_trait());
    }

    fn as_trait(&self) -> &Trait;
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }

    fn as_trait(&self) -> &Trait {
        self as &Trait
    }
}

你可以去游乐场试试。 (trait objects)

的加强版:

助手 as_trait 函数可以放在一个辅助特性中,该特性可以为所有试图实现 TraitSized 类型全面实现。然后 Trait 的实现者不必做任何特殊的事情并且转换工作。

fn use_trait(x: &Trait) {
    println!("object says {}", x.needed());
}

trait Trait : AsTrait {
    fn needed(&self) -> &str;

    fn provided(&self) where Self : AsTrait {
        use_trait(self.as_trait());
    }
}

trait AsTrait {
    fn as_trait(&self) -> &Trait;
}

impl<T : Trait + Sized> AsTrait for T {
    fn as_trait(&self) -> &Trait { self }
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }
}

fn main() {
    Struct().provided();
}

(在 play 上)。

也可以将 provided 简单地放在辅助特征中,但这样就必须动态分派给 Self 的其他方法,这是不必要的。


更新:实际上,重点是仍然可以覆盖 provided.

现在可以通过将其通用化来进一步改进上述内容。有 std::makrer::Unsize,在撰写本文时还不稳定。我们做不到

trait Trait : Unsize<Trait>

因为Rust不允许CRTP,幸好在方法上加上约束就够了。所以

fn use_trait(x: &Trait) {
    println!("object says {}", x.needed());
}

trait Trait {
    fn needed(&self) -> &str;

    fn provided(&self) where Self: AsObj<Trait> {
        use_trait(self.as_obj());
    }
}

trait AsObj<Tr: ?Sized> {
    fn as_obj(&self) -> &Trait;
}

// For &'a Type for Sized Type
impl<Type: Trait> AsObj<Trait> for Type {
    fn as_obj(&self) -> &Trait { self }
}

// For trait objects
impl AsObj<Trait> for Trait {
    fn as_obj(&self) -> &Trait { self }
}

struct Struct();

impl Trait for Struct {
    fn needed(&self) -> &str {
        "Hello, world!"
    }
    
    fn provided(&self) {
        println!("Aber dieses Objekt sagt Grüß Gott, Welt!"); // pardon my German, it is rusty.
    }
}

fn main() {
    let s: &Trait = &Struct();
    s.provided();
}

(在 play 上)

这最终使其对其他版本的实现者透明。

另见 this users thread