我有多种使用相似方法的类型。我想通过编写一个接口来抽象它们,就像在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())
}
但是,我将列举Rust的将Rust特征视为等同于OOP接口是错误的。
trait
的一些特殊性。调度静态发送当静态分配特征时,运行时没有开销。这等效于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中使用接口或在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();
}
的一个很大的优点是,您可以为数据实现特征,而无需修改数据。相反,在经典的面向对象的语言中,必须修改类以实现另一个接口。换句话说,您可以实现自己的外部数据特征。
This separation is true also at the lowest level。如果是动态调度,则为该方法提供两个指针:一个用于数据,另一个用于方法(vtable)。默认实现
trait比经典接口有更多的东西:它可以提供方法的默认实现(就像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中模拟继承机制,但是使用惯用设计而不是将语言扭曲为一种外来的思考方式(这将毫无用处地增加代码的复杂性)是一个更好的主意。
] >您应该阅读Rust书籍中的the chapter about traits,以了解有关此主题的更多信息。