你如何在 Rust 中声明一个接口? [英] How do you declare an interface in Rust?

查看:36
本文介绍了你如何在 Rust 中声明一个接口?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have multiple types with similar methods. I want to abstract over them by writing an interface, like I would in 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 不提供对多种类型进行抽象的可能性吗?

However, there is no interface keyword in Rust. Doesn't Rust offer the possibility to abstract over multiple types?

推荐答案

TL;DR:Rust 中最接近接口的是 trait.但是,不要不要期望它在所有点上都与界面相似.我的回答并不是详尽无遗,而是提供了一些与来自其他语言的答案进行比较的元素.

TL;DR: The closest to interface in Rust is a trait. However, do not expect it to be similar in all point to an interface. My answer does not aim to be exhaustive but gives some elements of comparison to those coming from other languages.

如果你想要一个类似于接口的抽象,你需要使用 Rust 的 traits:

If you want an abstraction similar to interface, you need to use Rust's traits:

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 trait 视为 OOP 接口的等价物是错误的.我将列举 Rust 的traits 的一些特殊性.

However, it is an error to see a Rust trait as an equivalent of OOP interface. I will enumerate some particularities of Rust's traits.

在 Rust 中,可以完成分派(在给定 trait 时使用正确的数据和方法)两种方式:

In Rust, the dispatch (i.e. using the right data and methods when given a trait) can be done in two ways:

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

When a trait is statically dispatched, there is no overhead at runtime. This is an equivalent of C++ templates; but where C++ uses SFINAE, the Rust compiler checks the validity using the "hints" we give to him:

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

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

With impl Shape, we say to the compiler that our function has a generic type parameter that implements Shape, therefore we can use the method Shape::area on our shape.

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

In this case, like in C++ templates, the compiler will generate a different function for each different type passed in.

在我们的第一个例子中:

In our first example:

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

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

the dispatch is dynamic. This is an equivalent to using an interface in C#/Java or an abstract class in C++.

在这种情况下,编译器不关心shape的类型.使用它的正确方法将在运行时确定,通常成本非常低.

In this case, the compiler does not care about the type of shape. The right thing to do with it will be determined at runtime, usually at a very slight cost.

如你所见,数据与实现分离;例如,C# 扩展方法.此外,trait 的效用之一是扩展值的可用方法:

As you see, the data is separated from the implementation; like, for example, C# extension methods. Moreover, one of the utilities of a trait is to extend the available methods on a value:

trait Hello {
    fn say_hello(&self);
}

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

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

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

A great advantage of this, is that you can implement a trait for a data without modifying the data. In contrast, in classical object oriented languages, you must modify the class to implement another interface. Said otherwise, you can implement your own traits for external data.

这种分离在最低层也是正确的.在动态调度的情况下,方法被赋予两个指针:一个用于数据,另一个用于方法(虚表).

This separation is true also at the lowest level. In case of dynamic dispatch, the method is given two pointers: one for the data, and another for the methods (the vtable).

trait 比经典接口还有一件事:它可以提供一个方法的默认实现(就像 Java 8 中的defender"方法).示例:

The trait has one more thing than a classic interface: it can provide a default implementation of a method (just like the "defender" method in Java 8). Example:

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

impl Hello for i32 {}

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

用经典的OOP的话来说,这就像一个没有变量成员的抽象类.

To use classic OOP words, this is like an abstract class without variable members.

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

The Rust trait's system is not an inheritance system. You cannot try to downcast, for example, or try to cast a reference on a trait to another trait. To get more information about this, see this question about upcasting.

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

Moreover, you can use the dynamic type to simulate some behavior you want.

虽然您可以用各种技巧模拟 Rust 中的继承机制,但最好使用惯用的设计,而不是将语言扭曲成一种外来的思维方式,这会无用地增加代码的复杂性.

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

You should read the chapter about traits in the Rust book to learn more about this topic.

这篇关于你如何在 Rust 中声明一个接口?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆