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

时间:2021-02-07 05:52:38

标签: rust serde-json

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

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

我应该注意到这个想法来自 this answer,它的不同之处在于我正在使用 serde_json 并且有兴趣删除代码重复,但答案可能仍然是“不可能,请检查 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 中工作。

2 个答案:

答案 0 :(得分:1)

不,traits 不能覆盖其他 traits 的实现。

语法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);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<块引用>

有没有办法避免为 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 不在范围内,则不会有任何问题。

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

答案 1 :(得分:1)

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

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

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

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

让我按照我的理解重新表述这个问题,我可能是错的。在我看来,你有一些复杂的 trait Formatter 和大量的方法,然后我们知道我们可以使用更简单的 trait 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,而无需您做任何额外的工作。

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

>