0

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

这是我今晚用 PrettyFormatter 做的一些实验serde_json,我想创建一个 PrettyFormatter 的版本,它只是改变了 Vec 的打印方式

我应该注意到这个想法来自这个答案,不同之处在于我正在使用 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 特征中工作。

4

2 回答 2

2

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

语法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不在范围内,则不会有任何问题。

请参阅此处的答案以获取更多信息和其他解决方案。

于 2021-02-07T06:15:02.737 回答
1

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

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

但这引入了我们的第二个问题。也就是说,你不会覆盖 Rust 中的 trait 方法。它根本行不通。您在代码示例中所做的是定义了两个不同的、不相关的特征函数begin_array_value(一个 inFormatter和一个 in 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,而无需您做任何额外的工作。

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

于 2021-02-07T06:17:12.503 回答