你如何在Rust中声明一个接口?

Luk*_*odt 8 interface rust

我有多种类型的方法.我想通过编写一个接口来抽象它们,就像我在Java中一样:

public interface Shape {
    public float area();
}

class Circle implements Shape {
    public float area() {
        return radius * radius * Math.PI;
    }

    public float radius;
}
Run Code Online (Sandbox Code Playgroud)

但是,interfaceRust中没有关键字.Rust不提供抽象多种类型的可能性吗?

Fre*_*ios 24

TL; DR:最接近Rust的接口是一个特性.不过,千万不能指望它在所有点的界面相似.我的回答并非旨在详尽无遗,而是提供一些与其他语言相比较的元素.


如果你想要一个类似于接口的抽象,你需要使用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())
}
Run Code Online (Sandbox Code Playgroud)

但是,将Rust特性视为OOP接口的等效项是错误的.我将列举Rust的一些特殊情况trait.

调度

在Rust中,调度(在给定特征时使用正确的数据和方法)可以通过两种方式完成:

静态调度

当静态调度特征时,运行时没有开销.这相当于C++模板; 但是在C++使用SFINAE的地方,Rust编译器使用我们给他的"提示"检查有效性:

fn display_area(shape: &impl Shape) {
    println!("area is {}", shape.area())
}
Run Code Online (Sandbox Code Playgroud)

有了impl Shape,我们说,我们的功能具有实现泛型类型参数编译器Shape,所以我们可以使用的方法Shape::area上我们shape.

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

动态调度

在我们的第一个例子:

fn display_area(shape: &dyn Shape) {
    println!("area is {}", shape.area())
}
Run Code Online (Sandbox Code Playgroud)

调度是动态的.这相当于在C#/ Java中使用接口或在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();
}
Run Code Online (Sandbox Code Playgroud)

这样做的一大优点是,您可以在不修改数据的情况下为数据实现特征.相反,在经典的面向对象语言中,您必须修改类以实现另一个接口.

这种分离在最低层也是如此.在动态调度的情况下,该方法有两个指针:一个用于数据,另一个用于方法(vtable).

默认实施

该特性还有一个比经典接口更多的东西:它可以提供方法的默认实现(就像Java 8中的"defender"方法一样).例:

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

impl Hello for i32 {}

fn main() {
    123.say_hello(); // call default implementation
}
Run Code Online (Sandbox Code Playgroud)

要使用经典的OOP单词,这就像一个没有变量成员的抽象类.

没有继承

Rust特征的系统不是继承系统.例如,您不能尝试向下转换,或尝试将特征的引用转换为另一个特征.要获得有关此信息的更多信息,请参阅有关向上转换的此问题.

此外,您可以使用动态类型来模拟您想要的某些行为.

虽然您可以使用各种技巧来模拟Rust中的继承机制,但使用惯用设计而不是将语言转换为外部思维方式更好的想法,这将无用地增加代码的复杂性.

您应该阅读Rust书中关于特征的章节,以了解有关此主题的更多信息.