图书馆作者选择的子类型的最佳选择是什么?为什么?

yaw*_*awn 1 polymorphism enums traits subtype rust

我有两种类型,A并且B,它们共享一个通用的接口T.我想编写可以同时使用AB通过接口工作的函数T.

在C++中,我会T是两者的一个抽象超AB,和写入功能接受类型的参数T.

在Rust,似乎我有两个很好的解决方案.

枚举

enum T { 
    A { /* ... */ },
    B { /* ... */ },
}

impl T {
   fn method(&self) {
       match *self {
           // ... 
       }
   } 
}
Run Code Online (Sandbox Code Playgroud)

在方法中T,我可以匹配变体以专门化类型,然后在访问变体成员时写下我想要的任何内容.然后我可以编写T直接接受类型参数的函数:

fn f(t: T) {
    // I can use t.method here
}
Run Code Online (Sandbox Code Playgroud)

性状

trait T {
    // ...
}

struct A { /* ... */ }
struct B { /* ... */ }

impl T for A {
   fn method(&self) {
      // ...  
   } 
}

impl T for B {
   fn method(&self) {
      // ...    
   }
}
Run Code Online (Sandbox Code Playgroud)

对于traits,类型已经专门用于方法定义.但是,要使用T.method,我需要编写一个通用函数:

fn f<X: T>(t: X) {
    // I can use t.method here
}
Run Code Online (Sandbox Code Playgroud)

让我困扰的是,虽然这两种解决方案都能很好地工作,但实现却暴露出来:f两个例子中的签名都不同.如果我用枚举编写一个库,但最终决定我真正想要的是特征,那么每个用户级类型的签名都需要改变!

鉴于这一事实,图书馆作者选择了什么选择,为什么?

请注意,我并不特别关心继承,我不打算将C++习惯用法导入Rust,我试图了解更好的替代方案.

She*_*ter 6

图书馆作者喜欢使用枚举或特征吗?

建筑商是否喜欢使用钉子或螺钉?

医生是否喜欢使用胶水,缝线或钉书钉?

这是一个荒谬的二分法.两种功能都存在并且都使用.人们使用正确的工具来完成手头的特定工作.

首先回过头来重新阅读Rust编程语言一章是Rust是面向对象的编程语言吗?.

简而言之,特征允许无限多态,而枚举是严格限制的.真的,这是主要的区别.查看您的问题域需要什么并使用正确的东西.

实现暴露:f两个示例中的签名都不同

是的,设计软件有时需要一些前期的思考,关怀和设计.

如果向用户公开枚举,然后添加,删除或修改变体,则需要更新该枚举的每个用法.

如果向用户公开特征,然后添加,删除或修改方法,则需要更新该特征的每个用法.

你选择导致这个问题的枚举或特征这一事实并没有什么特别之处.您还可以重命名方法,添加,删除或重新排序参数.有很多方法可以给用户带来麻烦.

如果您更改API,您的用户将受到影响.

我需要编写一个通用函数

不需要,但出于性能原因,您可能应该默认使用它.您可以使用特征对象fn f(t: &T),fn f(t: Box<T>)如果<T>它引起惊慌.

用枚举写一个库,但最终决定我真正想要的是特质

既然你知道你不必总是选择其中一个,也意识到你可以一起使用它们:

enum Pet {
    Cat,
    Dog,
    Other(Box<Animal>),
}

trait Animal {}
Run Code Online (Sandbox Code Playgroud)
trait TimeSource {
    fn now() -> u64;
}

enum Builtin {
    Ntp,
    Sundial,
}

impl TimeSource for Builtin {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

个人观点方面,如果我仍然是我的代码原型并且不清楚哪个选择更好,我可能会默认使用特征.无界多态性更符合我对依赖注入和测试风格的偏好.