我有多种具有相似方法的类型。我想通过编写一个接口(interface)来抽象它们,就像我在 Java 中那样:
public interface Shape {
public float area();
}
class Circle implements Shape {
public float area() {
return radius * radius * Math.PI;
}
public float radius;
}
但是,Rust 中没有interface
关键字。 Rust 不提供对多种类型进行抽象的可能性吗?
最佳答案
TL;DR:Rust 中最接近接口(interface)的是特征。但是,不要期望它在指向接口(interface)的所有方面都相似。我的回答并不旨在详尽无遗,而是提供了一些与来自其他语言的回答进行比较的元素。
如果你想要一个类似于接口(interface)的抽象,你需要使用Rust的trait
小号:
trait Shape {
fn area(&self) -> f32;
}
struct Circle {
radius: f32,
}
impl Shape for Circle {
fn area(&self) -> f32 {
self.radius.powi(2) * std::f32::consts::PI
}
}
struct Square {
side: f32,
}
impl Shape for Square {
fn area(&self) -> f32 {
self.side.powi(2)
}
}
fn main() {
display_area(&Circle { radius: 1. });
display_area(&Square { side: 1. });
}
fn display_area(shape: &dyn Shape) {
println!("area is {}", shape.area())
}
但是,将 Rust 特征视为等同于 OOP 接口(interface)是错误的。我将列举 Rust 的一些特殊性 trait
调度
在 Rust 中,调度(即在给定特征时使用正确的数据和方法)可以完成 in two ways :
静态调度
静态分派(dispatch)特征时,运行时没有开销。这相当于 C++ 模板;但是在 C++ 使用 SFINAE 的地方,Rust 编译器使用我们给他的“提示”检查有效性:
fn display_area(shape: &impl Shape) {
println!("area is {}", shape.area())
}
与 impl Shape
,我们告诉编译器我们的函数有一个实现了 Shape
的泛型类型参数, 因此我们可以使用方法 Shape::area
在我们的 shape
.
在这种情况下,就像在 C++ 模板中一样,编译器将为传入的每种不同类型生成不同的函数。
动态调度
在我们的第一个例子中:
fn display_area(shape: &dyn Shape) {
println!("area is {}", shape.area())
}
调度是动态的。这相当于在 C#/Java 中使用接口(interface)或在 C++ 中使用抽象类。
在这种情况下,编译器不关心 shape
的类型.使用它的正确做法将在运行时确定,通常成本非常低。
数据与实现分离
如您所见,数据与实现是分离的;例如,C# 扩展方法。此外,特征的一个用途是扩展值的可用方法:
trait Hello {
fn say_hello(&self);
}
impl Hello for &'static str {
fn say_hello(&self) {
println!("Hello, {}!", *self)
}
}
fn main() {
"world".say_hello();
}
这样做的一大优势是,您可以在不修改数据的情况下为数据实现特征。相反,在经典的面向对象语言中,您必须修改类以实现另一个接口(interface)。换句话说,您可以为外部数据实现自己的特征。
This separation is true also at the lowest level .在动态调度的情况下,该方法被赋予两个指针:一个用于数据,另一个用于方法(vtable)。
默认实现
trait 比经典接口(interface)多了一个东西:它可以提供一个方法的默认实现(就像 Java 8 中的“defender”方法)。示例:
trait Hello {
fn say_hello(&self) {
println!("Hello there!")
}
}
impl Hello for i32 {}
fn main() {
123.say_hello(); // call default implementation
}
用经典的 OOP 的话来说,这就像一个没有变量成员的抽象类。
无继承
Rust trait 的系统不是继承系统。例如,您不能尝试向下转型,或尝试将一个特征的引用转换为另一个特征。要获得有关此的更多信息,请参阅 this question about upcasting .
此外,您可以使用 dynamic type模拟你想要的一些行为。
虽然您可以使用各种技巧模拟 Rust 中的继承机制,但使用惯用设计而不是将语言扭曲为一种无益地增加代码复杂性的外来思维方式是更好的主意。强>
你应该阅读 the chapter about traits在 Rust 书中了解有关此主题的更多信息。
关于interface - 你如何在 Rust 中声明一个接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55813962/