是否可以覆盖 Rust 中的默认特征实现?

Is it possible to override default trait implementations in Rust?

我并没有在高级特征的道路上徘徊太多,但我想知道是否有可能通过创建一个特征来重写/复制和粘贴九个函数,这个特征只覆盖一个或三个函数复杂的特征。

这是我今晚在 serde_jsonPrettyFormatter 上做的一些实验,我想在其中创建一个 PrettyFormatter 版本,它只改变 Vec 的打印方式。

我应该注意到这个想法来自 which differs in that I'm consuming serde_json and interested in removing code duplication but likely the answer is still "not possible, check the RFC”。不能重复使用已经可用的代码似乎很浪费。

这是我似乎失败的最小案例:

trait Formatter {
    fn begin_array_value(&self) {
        println!("Formatter called");
    }
    
    fn two(&self) {
        println!("two")
    }
    
    // ... pretend there are a few more functions ...
    
    fn ten(&self) {
        println!("ten")
    }

}

trait PrettyFormatter: Formatter {
    fn begin_array_value(&self) {
        println!("I'm pretty!");
    }
}

struct MyFormatter { }

// This fails:
impl PrettyFormatter for MyFormatter { }
// This works:
//impl Formatter for MyFormatter { }

fn main() {
    let formatter = MyFormatter { };
    formatter.begin_array_value();
}

具体错误是这样的:

Standard Error

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `MyFormatter: Formatter` is not satisfied
  --> src/main.rs:16:6
   |
8  | trait PrettyFormatter: Formatter {
   |                        --------- required by this bound in `PrettyFormatter`
...
16 | impl PrettyFormatter for MyFormatter { }
   |      ^^^^^^^^^^^^^^^ the trait `Formatter` is not implemented for `MyFormatter`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

可以复制并粘贴 ~320 行,但我非常喜欢编写尽可能少的代码。如果这个 以某种方式可能,我想提交一个 PR 到那个板条箱,这样其他人就可以从 PrettyFormatter 特征开始工作。

不,特征不能覆盖其他特征的实现。

语法 trait PrettyFormatter: Formatter { ... } 暗示类似继承的关系。 : 之后的任何内容都是 约束 ,具体类型要满足的要求才能实现它。这意味着任何想要实现 PrettyFormatter 的东西也必须实现 Formatter。考虑到这一点,PrettyFormatter::begin_array_valueFormatter::begin_array_value.

没有关系

您可以为您的 MyFormatter 结构实现这两个特征:

impl Formatter for MyFormatter { }
impl PrettyFormatter for MyFormatter { }

但尝试调用 formatter.begin_array_value() 时会遇到错误,表明调用不明确:

error[E0034]: multiple applicable items in scope
  --> src/main.rs:33:15
   |
33 |     formatter.begin_array_value();
   |               ^^^^^^^^^^^^^^^^^ multiple `begin_array_value` found
   |
note: candidate #1 is defined in an impl of the trait `Formatter` for the type `MyFormatter`
  --> src/main.rs:2:5
   |
2  |     fn begin_array_value(&self) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `PrettyFormatter` for the type `MyFormatter`
  --> src/main.rs:19:5
   |
19 |     fn begin_array_value(&self) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #1
   |
33 |     Formatter::begin_array_value(&formatter);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #2
   |
33 |     PrettyFormatter::begin_array_value(&formatter);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Is there a way to avoid re-implementing the nine other functions for PrettyFormatter?

您必须实施它们,但您可以像这样推迟 Formatter 实施:

trait PrettyFormatter: Formatter {
    fn begin_array_value(&self) {
        Formatter::begin_array_value(self);
    }

    fn two(&self) {
        Formatter::two(self);
    }

    // ... others
    
    fn ten(&self) {
        Formatter::ten(self);
    }
}

模糊的函数调用问题仍然存在,但前提是两个特征都在范围内。如果原始 Formatter 不在范围内,则不会有任何问题。

有关详细信息和其他解决方案,请参阅

您正在以一种非常 C# 的心态来处理这个问题,这会导致问题。特征不是接口,尽管它们偶尔会像接口一样。一方面,trait PrettyFormatter: Formatter 而不是 说“任何实施 PrettyFormatter 的人都会自动实施 Formatter”。事实上,它说的恰恰相反:“唯一允许 实现 PrettyFormatter 的类型是那些已经实现 Formatter 的类型”。所以,如果你想为你的类型实现 PrettyFormatter,你需要两者都做。

impl PrettyFormatter for MyFormatter { }
impl Formatter for MyFormatter { }

但这引入了我们的第二个问题。也就是说,您不会覆盖 Rust 中的特征方法。那样根本行不通。您在代码示例中所做的是定义两个不同的、不相关的特征函数,称为 begin_array_value(一个在 Formatter 中,一个在 PrettyFormatter 中),如果您尝试调用其中一个, Rust 会感到困惑,因为有两个函数同名。同样,这不是语言的缺陷;这是 Rust 避开 C# 和 Java 等语言背后的原则,转而采用不同的抽象模式。

这让我们回到了您的原始观点以及我们应该如何处理代码重用,不幸的是,我可能没有足够的关于您的特定用例的信息来回答您的问题。您的最小示例从不使用 self,因此在这种情况下,我认为问题中的函数甚至不应该是特征函数,而应该是独立函数。

让我按照我的理解重新表述这个问题,我在这方面可能是错误的。在我看来你有一些复杂的特征 Formatter 和大量的方法,然后我们知道我们可以使用一个更简单的特征 PrettyFormatter 来实现所有这些方法,它只需要一些更简单的方法.如果这一切都正确,那么我建议全面实施。

trait Formatter {
  // A zillion methods
}

trait PrettyFormatter { // N.B. No supertrait
  // A nice subset of a zillion methods
}

impl<T> Formatter for T where T : PrettyFormatter {
  // Here, implement all zillion methods for Formatter using
  // only the few from PrettyFormatter
}

现在,您选择的任何类型都可以实现 PrettyFormatter,这样做时,它会自动实现 Formatter,无需您做任何额外的工作。

我还要强调的是,您不应该对两个特征之间的特征函数使用相同的名称,因为这只会使调用这些函数变得更加困难,并且对于您的代码的任何用户来说都非常不直观。