是否可以覆盖 Rust 中的默认特征实现?
Is it possible to override default trait implementations in Rust?
我并没有在高级特征的道路上徘徊太多,但我想知道是否有可能通过创建一个特征来重写/复制和粘贴九个函数,这个特征只覆盖一个或三个函数复杂的特征。
这是我今晚在 serde_json
的 PrettyFormatter 上做的一些实验,我想在其中创建一个 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_value
与 Formatter::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
,无需您做任何额外的工作。
我还要强调的是,您不应该对两个特征之间的特征函数使用相同的名称,因为这只会使调用这些函数变得更加困难,并且对于您的代码的任何用户来说都非常不直观。
我并没有在高级特征的道路上徘徊太多,但我想知道是否有可能通过创建一个特征来重写/复制和粘贴九个函数,这个特征只覆盖一个或三个函数复杂的特征。
这是我今晚在 serde_json
的 PrettyFormatter 上做的一些实验,我想在其中创建一个 PrettyFormatter 版本,它只改变 Vec 的打印方式。
我应该注意到这个想法来自
这是我似乎失败的最小案例:
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_value
与 Formatter::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
,无需您做任何额外的工作。
我还要强调的是,您不应该对两个特征之间的特征函数使用相同的名称,因为这只会使调用这些函数变得更加困难,并且对于您的代码的任何用户来说都非常不直观。