【问题标题】:Can I conditionally provide a default implementation of a trait function?我可以有条件地提供特征函数的默认实现吗?
【发布时间】:2019-04-13 19:23:00
【问题描述】:

我有以下特点:

trait MyTrait {
    type A;
    type B;

    fn foo(a: Self::A) -> Self::B;

    fn bar(&self);
}

还有其他函数,例如 bar,必须始终由 trait 的用户实现。

我想给foo 一个默认实现,但仅限于A = B 类型。

伪 Rust 代码:

impl??? MyTrait where Self::A = Self::B ??? {
    fn foo(a: Self::A) -> Self::B {
        a
    }
}

这是可能的:

struct S1 {}

impl MyTrait for S1 {
    type A = u32;
    type B = f32;

    // `A` is different from `B`, so I have to implement `foo`
    fn foo(a: u32) -> f32 {
        a as f32
    }

    fn bar(&self) {
        println!("S1::bar");
    }
}

struct S2 {}

impl MyTrait for S2 {
    type A = u32;
    type B = u32;

    // `A` is the same as `B`, so I don't have to implement `foo`,
    // it uses the default impl

    fn bar(&self) {
        println!("S2::bar");
    }
}

这在 Rust 中可能吗?

【问题讨论】:

  • 在尝试回答时,我遇到了another question,这可能很有趣。
  • 不接受self(或引用它)或返回Self的特征函数是否有意义?
  • @Cerberus 我想不会,但这只是简化的示例。我将对其进行编辑以使其更有意义。

标签: rust


【解决方案1】:

您可以通过引入冗余类型参数在 trait 定义本身中提供默认实现:

trait MyTrait {
    type A;
    type B;

    fn foo<T>(a: Self::A) -> Self::B
    where
        Self: MyTrait<A = T, B = T>,
    {
        a
    }
}

这个默认实现可以被个别类型覆盖。但是,专门的版本会从 trait 上的 foo() 的定义继承 trait 绑定,因此您只能在 A == B 时实际调用该方法:

struct S1;

impl MyTrait for S1 {
    type A = u32;
    type B = f32;

    fn foo<T>(a: Self::A) -> Self::B {
        a as f32
    }
}

struct S2;

impl MyTrait for S2 {
    type A = u32;
    type B = u32;
}

fn main() {
    S1::foo(42);  // Fails with compiler error
    S2::foo(42);  // Works fine
}

Rust 也有一个unstable impl specialization feature,但我不认为它可以用来实现你想要的。

【讨论】:

  • 为什么又在问题中添加了“附加方法”?这真的很重要吗?
  • @hellow 它可能很重要。举个例子,另一个答案中代码的固定版本实际上适用于没有任何其他方法的情况,否则会变得很麻烦。
  • 谢谢,但由于您已经描述的原因,此解决方案对我不起作用 - 如果 A != B,则无法调用 foo,即使您提供自己的 @ 实现987654328@.
【解决方案2】:

这就够了吗?:

trait MyTrait {
    type A;
    type B;

    fn foo(a: Self::A) -> Self::B;
}

trait MyTraitId {
    type AB;
}

impl<P> MyTrait for P
where
    P: MyTraitId
{
    type A = P::AB;
    type B = P::AB;

    fn foo(a: Self::A) -> Self::B {
        a
    }
}

struct S2;

impl MyTraitId for S2 {
    type AB = i32;
}

Rust Playground

如前所述,如果 MyTraitMyTraitId 无法为其提供实现的其他方法一样,它将遇到问题。

【讨论】:

  • 问题是任何类型P 都可能有多个MyTraitId 实现,因此您最终会得到MyTrait 的多个冲突实现。可以通过使T 成为MyTraitId 的关联类型而不是类型参数来实现此功能,但这仍然只是部分解决方案,因为您必须实现MyTrait 中的所有方法全面实施。
  • 请不要发布未经测试的代码。而是使用the playground 轻松快速地检查您的代码。这让您和我们的生活更轻松 :)
  • 这可行,但如果 trait 中还有其他没有默认实现的函数怎么办?最初我有评论表明还有其他功能,现在我编辑了我的问题并添加了第二个功能以使其清晰。
【解决方案3】:

扩展 user31601's answer 并使用 Sven Marnach 的评论中的注释,这是一个使用“委托方法”模式的具有附加功能的 trait 实现:

trait MyTrait {
    type A;
    type B;

    fn foo(a: Self::A) -> Self::B;
    fn bar();
}

trait MyTraitId {
    type AB;
    fn bar_delegate();
}

impl<P> MyTrait for P
where
    P: MyTraitId,
{
    type A = P::AB;
    type B = P::AB;

    fn foo(a: Self::A) -> Self::B {
        a
    }
    fn bar() {
        <Self as MyTraitId>::bar_delegate();
    }
}

struct S2;

impl MyTraitId for S2 {
    type AB = i32;
    fn bar_delegate() {
        println!("bar called");
    }
}

fn main() {
    <S2 as MyTrait>::bar(); // prints "bar called"
}

Playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    • 2012-04-12
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 2019-06-06
    相关资源
    最近更新 更多