我有两种类型,A
和B
,它们共享一个公共(public)接口(interface)T
。我想通过A
接口(interface)编写可同时与B
和T
一起使用的函数。
在C++中,我希望T
是A
和B
的抽象父类(super class),并编写接受T
类型的参数的函数。
在Rust中,似乎我有两个不错的解决方案。
枚举
enum T {
A { /* ... */ },
B { /* ... */ },
}
impl T {
fn method(&self) {
match *self {
// ...
}
}
}
在
T
的方法中,我可以匹配变体以专门化类型,然后在访问变体成员时写任何我想要的东西。然后,我可以编写直接接受
T
类型的参数的函数:fn f(t: T) {
// I can use t.method here
}
特质
trait T {
// ...
}
struct A { /* ... */ }
struct B { /* ... */ }
impl T for A {
fn method(&self) {
// ...
}
}
impl T for B {
fn method(&self) {
// ...
}
}
对于特征,类型已经专门用于方法定义。但是,要使用
T.method
,我需要编写一个通用函数:fn f<X: T>(t: X) {
// I can use t.method here
}
让我困扰的是,虽然这两种解决方案都可以完美地工作,但是实现却暴露了出来:
f
的签名在两个示例中都不同。如果我写了一个带有枚举的库,但最终决定我真正想要的是特征,则需要更改每个用户级别的类型签名!考虑到这一事实,图书馆作者会选择什么选择,为什么?
请注意,我并不特别在乎继承,我不希望将C++习惯用法导入Rust,而是试图了解更好的替代方法。
最佳答案
这是荒谬的二分法。两种功能都存在并且都被使用。人们使用正确的工具来完成手头的特定工作。
首先返回并重新阅读The Rust Programming Language章Is Rust an Object-Oriented Programming Language?。
简而言之,特征允许无限制的多态性,而枚举则严格限制。真的,那是主要区别。查看您的问题域需要什么并使用正确的方法。
是的,设计软件有时需要进行一些前期的思考,维护和设计。
如果您向用户公开枚举,然后添加,删除或修改变体,则该枚举的每种用法都需要更新。
如果向用户公开特征,然后添加,删除或修改方法,则该特征的每种用法都需要更新。
在导致此问题的枚举或特征之间进行选择这一事实并没有什么特别的。您还可以重命名方法,添加,删除或重新排列参数。有很多方法可以给您的用户带来麻烦。
如果更改您的API,您的用户将受到的影响。
您不需要,但是出于性能方面的考虑,您可能应该默认使用它。如果fn f(t: &T)
引起惊吓,则可以使用fn f(t: Box<T>)
或<T>
之类的特征对象。
既然您知道不必总是选择一个或另一个,那么您也可以将它们一起使用:
enum Pet {
Cat,
Dog,
Other(Box<Animal>),
}
trait Animal {}
trait TimeSource {
fn now() -> u64;
}
enum Builtin {
Ntp,
Sundial,
}
impl TimeSource for Builtin {
// ...
}
从个人观点出发,如果我仍在对代码进行原型(prototype)设计,并且不清楚哪种选择更好,我可能会默认使用特征。无限多态性更适合我对依赖项注入(inject)和测试样式的偏爱。