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 参与实现和使用特征。这和我上面提到的“独立命名空间”密切相关
在我看来,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 参与实现和使用特征。这和我上面提到的“独立命名空间”密切相关