我有多种方法相似的类型.我想通过编写一个接口来抽象它们,就像在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 迹中最接近界面是一种特征.然而,not希望它在所有方面都与接口类似.我的回答并非详尽无遗,而是提供了一些与其他语言进行比较的元素.


如果你想要一个类似于界面的抽象,你需要使用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())
}

然而,it is an error to see a Rust trait as an equivalent of OOP interface.我将列举Rust's trait的一些特殊性.

Dispatch

在Rust中,调度(i.e.在给定特征时使用正确的数据和方法)可以完成in two ways:

静态调度

当一个trait被静态调度时,运行时没有开销.这是一个等价的C++模板;但是,C++使用SFIEAE时,RIST编译器使用我们给他的"提示"来判断有效性:

fn display_area(shape: &impl Shape) {
    println!("area is {}", shape.area())
}

对于impl Shape,我们告诉编译器我们的函数有一个实现Shape的泛型类型参数,因此我们可以在shape上使用方法Shape::area.

在这种情况下,像C++模板一样,编译器将为传入的每个不同类型生成不同的函数.

动态调度

在我们的第一个例子中:

fn display_area(shape: &dyn Shape) {
    println!("area is {}", shape.area())
}

调度是动态的.这相当于在C++ java中使用接口或者C++中的抽象类.

在本例中,编译器不关心shape的类型.使用它的正确方法将在运行时确定,通常成本很低.

Separation between data and implementation

如您所见,数据与实现是分离的;比如,C#扩展方法.此外,trait的一个实用功能是在一个值上扩展可用的方法:

trait Hello {
    fn say_hello(&self);
}

impl Hello for &'static str {
    fn say_hello(&self) {
        println!("Hello, {}!", *self)
    }
}

fn main() {
    "world".say_hello();
}

这样做的一大优点是,您可以在不修改数据的情况下实现数据的特征.相比之下,在classic 的面向对象语言中,必须修改类以实现另一个接口.换言之,您可以为外部数据实现自己的特性.

This separation is true also at the lowest level.在动态分派的情况下,方法有两个指针:一个用于数据,另一个用于方法(vtable).

Default implementation

与classic 接口相比,trait还有一个优点:它可以提供方法的默认实现(就像Java8中的"defender"方法).例子:

trait Hello {
    fn say_hello(&self) {
        println!("Hello there!")
    }
}

impl Hello for i32 {}

fn main() {
    123.say_hello(); // call default implementation
}

用classic 的OOP语言来说,这就像一个没有变量成员的抽象类.

No inheritance

Rust 特性的系统不是遗传系统.例如,你不能试图贬低,或者试图把一个特征的参照物与另一个特征联系起来.欲了解更多信息,请参阅this question about upcasting.

此外,您可以使用dynamic type来模拟所需的某些行为.

While you can simulate the inheritance mechanism in Rust with various tricks, this is a better idea to use idiomatic designs instead of twist the language to a foreign way of thinking that will uselessly make grow the complexity of code.

你应该阅读铁 rust 书中的the chapter about traits来了解更多关于这个话题的内容.

Rust相关问答推荐

基于对vec值的引用从该值中删除该值

在Rust中赋值变量有运行时开销吗?

铁 rust 中的泛型:不能将`<;T作为添加>;::Output`除以`{Float}`

有没有可能让泛型Rust T总是堆分配的?

当发送方分配给静态时,Tokio MPSC关闭通道

为什么&;mut buf[0..buf.len()]会触发一个可变/不可变的borrow 错误?

什么时候使用FuturesOrdered?

Rust面向对象设计模式

找不到 .has_func 或 .get_func 的 def

Rust:为什么 Pin 必须持有指针?

中文优化标题:跳出特定循环并返回一个值

OpenGL 如何同时渲染无纹理的四边形和有纹理的四边形

如何使用 Rust Governor 为每 10 秒 10 个请求创建一个 RateLimiter?

有什么方法可以通过使用生命周期来减轻嵌套生成器中的当生成器产生时borrow 可能仍在使用错误?

为什么数组不像向量那样在 for 块之后移动?

如何将 Rust 中的树状 struct 展平为 Vec<&mut ...>?

在空表达式语句中移动的值

在 Rust 中枚举字符串的最佳方式? (字符()与 as_bytes())

Rust 中的运行时插件

为什么 u64::trailing_zeros() 在无分支工作时生成分支程序集?