库编写者选择的替代子类型的首选方法是什么,为什么?
What is the favored alternative to subtyping that library writers choose, and why?
我有两种类型,A
和 B
,它们共享一个公共接口 T
。我想通过接口 T
.
编写与 A
和 B
一起工作的函数
在 C++ 中,我会让 T
成为 A
和 B
的抽象超类,并编写接受类型为 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,我试图了解更好的选择是什么。
Do library writers favor using enums or traits?
Do builders favor using nails or screws?
Do doctors favor using glue, stitches, or staples?
这是一个荒谬的二分法。这两种能力都存在并且都被使用。人们使用正确的工具来完成手头的特定工作。
首先返回并重新阅读 The Rust Programming Language chapter Is Rust an Object-Oriented Programming Language?。
简而言之,特征允许无限制的多态性,而枚举是严格限制的。真的,这是主要的区别。查看您的问题域需要什么并使用正确的东西。
the implementation is exposed: the signature of f
is different in both examples
是的,设计软件有时需要一定的前期思考、细心和设计。
如果您向用户公开一个枚举,然后添加、删除或修改一个变体,则该枚举的每次使用都需要更新。
如果您向用户公开一个特征,然后添加、删除或修改一个方法,则该特征的每次使用都需要更新。
您在导致此问题的枚举或特征之间进行选择并没有什么特别之处。您还可以重命名该方法,添加、删除或重新排序您的参数。 很多 方法会给您的用户带来麻烦。
如果您更改 API,您的用户将受到影响。
I need to write a generic function
您不需要,但出于性能原因,您可能应该默认使用它。如果 <T>
引起惊愕,您可以使用像 fn f(t: &T)
或 fn f(t: Box<T>)
这样的特征对象。
write a library with enums, but eventually decide what I really wanted was traits
现在您知道您不必总是选择其中之一,也意识到您可以一起使用它们:
enum Pet {
Cat,
Dog,
Other(Box<Animal>),
}
trait Animal {}
trait TimeSource {
fn now() -> u64;
}
enum Builtin {
Ntp,
Sundial,
}
impl TimeSource for Builtin {
// ...
}
个人意见,如果我仍在制作我的代码原型并且不清楚哪个选择更好,我可能会默认使用特征。无限多态性更符合我对依赖注入和测试风格的偏好。
我有两种类型,A
和 B
,它们共享一个公共接口 T
。我想通过接口 T
.
A
和 B
一起工作的函数
在 C++ 中,我会让 T
成为 A
和 B
的抽象超类,并编写接受类型为 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,我试图了解更好的选择是什么。
Do library writers favor using enums or traits?
Do builders favor using nails or screws?
Do doctors favor using glue, stitches, or staples?
这是一个荒谬的二分法。这两种能力都存在并且都被使用。人们使用正确的工具来完成手头的特定工作。
首先返回并重新阅读 The Rust Programming Language chapter Is Rust an Object-Oriented Programming Language?。
简而言之,特征允许无限制的多态性,而枚举是严格限制的。真的,这是主要的区别。查看您的问题域需要什么并使用正确的东西。
the implementation is exposed: the signature of
f
is different in both examples
是的,设计软件有时需要一定的前期思考、细心和设计。
如果您向用户公开一个枚举,然后添加、删除或修改一个变体,则该枚举的每次使用都需要更新。
如果您向用户公开一个特征,然后添加、删除或修改一个方法,则该特征的每次使用都需要更新。
您在导致此问题的枚举或特征之间进行选择并没有什么特别之处。您还可以重命名该方法,添加、删除或重新排序您的参数。 很多 方法会给您的用户带来麻烦。
如果您更改 API,您的用户将受到影响。
I need to write a generic function
您不需要,但出于性能原因,您可能应该默认使用它。如果 <T>
引起惊愕,您可以使用像 fn f(t: &T)
或 fn f(t: Box<T>)
这样的特征对象。
write a library with enums, but eventually decide what I really wanted was traits
现在您知道您不必总是选择其中之一,也意识到您可以一起使用它们:
enum Pet {
Cat,
Dog,
Other(Box<Animal>),
}
trait Animal {}
trait TimeSource {
fn now() -> u64;
}
enum Builtin {
Ntp,
Sundial,
}
impl TimeSource for Builtin {
// ...
}
个人意见,如果我仍在制作我的代码原型并且不清楚哪个选择更好,我可能会默认使用特征。无限多态性更符合我对依赖注入和测试风格的偏好。