Rust 特征是否与 Java 接口相同

Is Rust trait the same as Java interface

在我看来,Rust 的 trait 与 Java 的 interface 是一回事——一组需要在对象上实现的函数。

将其命名为 trait 而不是 interface 是否有技术原因,或者只是一些偏好?

Interface是面向对象编程的一个概念。当您说一个对象类型实现一个接口时,您指的是该类型的 属性。它说类型遵循一定的合同。

Rust 不是一种面向对象的语言。 traits 不完全是接口。当你说一个结构有一些 trait 时,这并不一定意味着它是结构的 属性,而是结构映射了特征的功能。

例如,您可以有一个内置的 rust 类型 [f32; 2] - 一个有两个值的数组。你可以有一个特征 Point:

trait Point {
  fn x(&self) -> f32;
  fn y(&self) -> f32;
}

作为特征的作者,您可以决定类型 [f32; 2] 很好地映射到该特征的功能。您可以为类型实现此特征:

impl Point for [f32; 2] {
  fn x(&self) -> f32 { self[0] }
  fn y(&self) -> f32 { self[1] }
}

这样做你并没有改变类型本身,而是说它具有你的特征。

这是一件事,你不能用接口做,但可以用特征做。我相信为 rust 选择 trait 而不是 interface 是为了强调这种区别并防止概念混淆。

Rust traits 和 Java 接口都解决了有多个可能的实现的问题,这些实现遵循一些 convention/protocol/interface 与 value/object 交互,而不将实现细节限制为 Java superclass 可以。它们可以用于许多相同的情况。但是,它们在许多细节上有所不同,主要是,这意味着 Rust 特性更强大:

  • Java 接口要求实现对象具有具有特定名称的方法。 Rust traits 有一个完全独立的命名空间。在Java中,两个接口可能无法一起实现:

    interface Foo {
      void someMethod();
    }
    interface Bar {
      int someMethod();
    }
    class TwoInterfaces implements Foo, Bar {
      public int someMethod();  // The return type must be void and also must be int
    }
    

    在 Rust 中,特征的每个实现都存在于它自己的 impl 块中,并且它们被认为是不同的。这意味着为一个类型实现一个特征不能与不同的特征冲突(除了在范围内同时具有两个特征的调用站点,必须使用函数语法而不是方法语法来消除歧义)。

  • Java traits可以有泛型(类型参数),SomeInterface<T>,但是一个对象只能实现一个接口一次。

    class TwoGenerics implements Foo<Integer>, Foo<String> {
      public void someMethod(??? value) {}  // Can't implement, must be one method
    }
    interface Foo<T> {
      void someMethod(T value);
    }
    

    在 Rust 中,可以为多种类型实现一个 trait,这些基本上被认为是不同的 trait:

    struct TwoGenerics;
    
    trait Foo<T> {
        fn some_method(&self, input: T);
    }
    
    impl Foo<i32> for TwoGenerics {
        fn some_method(&self, input: i32) {}
    }
    impl Foo<String> for TwoGenerics {
        fn some_method(&self, input: String) {}
    }
    

    要获得类似 Java 的行为,要求任何实现类型都有一个特定类型,您可以定义一个 关联类型 而不是泛型:

    struct Thing;
    
    trait Foo {
        type Input;
        fn some_method(&self, input: Self::Input);
    }
    
    impl Foo for Thing {
        type Input = i32;
        fn some_method(&self, input: i32) {}
    }
    
  • 在 Rust 中,如果您定义了特征,则可以为您未定义的类型实现特征。因此,您可以在您的 crate 中定义一个特征,然后在标准库(或您依赖的其他库)中为相关类型实现它,例如序列化、随机生成、遍历等。在 Java 中,解决方法例如运行-需要时间类型检查才能获得类似的结果。

  • 在 Rust 中,特征可能具有适用于满足某些条件(边界)的所有类型的泛型实现,或者仅当其 时才适用于特定泛型类型的实现parameters 合适。例如,在标准库中,有(大约)

    impl<T> Clone for Vec<T> where T: Clone {...}
    

    所以 Vec 是可克隆的当且仅当它的内容是。在 Java 中,class 不能有条件地实现接口,这对任何递归 属性 都是有问题的:例如,list instanceof Serializable 可能为真,而列表将无法序列化,因为它的一个或多个元素不可序列化。

  • Rust traits 可能有关联的常量、类型和非方法函数(类似于 Java static 方法),所有这些对于每个实现类型都可能不同.当 Java 个接口有 static 个成员时,它们对整个接口只有一个实现。

    例如,Rust 中的 Default 特性允许通过调用 Default::default()T::default() 构造任何实现类型的新实例。在 Java 中,您需要创建一个单独的“工厂”接口来执行此操作。

  • Rust 特征可以具有接受多个输入(或产生输出)的函数,这些输入也是实现类型。 Java 接口不能引用实现类型;他们只能添加不需要相同的类型参数。 (另一方面,Java 有 subclassing(子类型化),而 Rust 没有,所以当你有一组不同具体类型但相同的实例时,情况必然更复杂超类型。)

可能还有更多细节可以提及,但我认为这些涵盖了很多您可以或必须以不同方式使用它们的方式,即使它们用于相同的任务也是如此。


至于 name “trait” 与 “interface”,这是由于现有的 concept of traits in computer science 被认为具有几个特定的​​属性,主要是没有 inheritance 也没有 overriding 参与实现和使用特征。这和我上面提到的“独立命名空间”密切相关