023---Rust能力养成系列之第4章 :特性的用法与形式(下)

前言

这里紧接着上篇的内容来说。

 

特性用法

特性也可以在其声明中说明依赖于其他特性,这就是所谓特性继承(trait inheritance),用法如下

// trait_inheritance.rs
trait Vehicle {
    fn get_price(&self) -> u64;
}
 
trait Car: Vehicle {
    fn model(&self) -> String;
}
 
struct TeslaRoadster {
    model: String,
    release_date: u16
}
 
impl TeslaRoadster {
    fn new(model: &str, release_date: u16) -> Self {
        Self { model: model.to_string(), release_date }
    }
}
 
impl Car for TeslaRoadster {
    fn model(&self) -> String {
        "Tesla Roadster I".to_string()
    }
}
 
fn main() {
    let my_roadster = TeslaRoadster::new("Tesla Roadster II", 2020);
    println!("{} is priced at ${}", my_roadster.model, my_roadster.get_price());
}

 

在上面的代码中,我们声明了两个特性:一个是Vehicle(相对广泛的特性),另一个一个是Car(相对具体的特性),显然依赖于Vehicle。由于TeslaRoadster是一辆汽车,可以为其实现Car特性。另外,请注意TeslaRoadster上的new方法,其使用Self作为返回类型,可被替换为从new返回的TeslaRoadster实例。Self只是特性在impl块中实现类型的一个简单别名,还可以用于创建其他类型,比如元组结构和枚举,也可以用于匹配表达式。我们编译一下这段代码:

023---Rust能力养成系列之第4章 :特性的用法与形式(下)

说明一点,后续文字会大量使用trait来替代特性这个词。

此时,看到上图这个错误了吧?在其定义中,Car trait指定了一个依赖关系,即任何实现该trait的类型也必须实现Vehicle trait Car: Vehicle。这里没有为TeslaRoadster实现Vehicle,Rust编译器以此报错。所以,必须添加相关的实现代码,如下

// trait_inheritance.rs
impl Vehicle for TeslaRoadster {
    fn get_price(&self) -> u64 {
        200_000   //underscore here is just for readability
    }
}

编译通过,结果如下

023---Rust能力养成系列之第4章 :特性的用法与形式(下)

与面向对象的语言类似,trait及其实现类似于接口(interface)和实现这些接口的类(class)。然而,需要注意的是,trait与interface在Rust中还是有明显区别的:

  • trait在Rust中具有某种形式的继承,实现(implementation)却没有。这意味着可以声明一个名为Panda的trait,需要另一个名为KungFu的trait,其有实现Panda的类型来实现(有点像绕口令,但意思上就是这样)。然而,类型本身没有任何类型的继承。因此,不是使用对象继承,而是使用类型组合,其依赖于trait继承来在代码中为现实实体建模。
  • 可以在任何地方编写trait实现块,而不必访问实际的类型
  • 可以在从内置基元类型到泛型类型的任何类型上实现trait
  • 在函数中不能隐式的将特征作为返回类型,这与在Java中可以将接口作为返回类型是不一样的。必须返回一个trait对象,相关语法是很明确的,我们会在讲到trait对象时介绍。

 

Trait的形式

在前面的例子中,我们已经见识了trait的最简单形式,但其作用远比表面上看到的要大。当遇到大型的代码库时,会发现有很多各种各样的特性。考虑到问题和相关程序的复杂性,简单的trait可能就不太适合了。Rust为我们提供了更多形式的特性,可以很好的模拟复杂问题。下面来看看一些标准库的trait,并试着对其进行分类,这样就能渐渐了解到什么时候使用什么。

 

Marker traits

在std::marker模块中定义的性状称为marker traits。这些trait没有任何方法,只是用它们的名称和一个空体声明。标准库中的示例包括Copy、Send和Sync。之所以被称为marker traits,因为它们被用来简单的标记一个类型属于一个特定的类别,以获得一些编译时的保障。标准库中的两个例子是Send和Sync,对大多数类型而言,这些trait在特定的时候自动实现,并传达关于哪些值可以安全的在线程间发送和共享的信息。有关进一步介绍,我们将在第8章 讲并发性时继续。

 

Simple traits

这就是上一小节介绍的简单的traits

trait Foo {
    fn foo();
}

 

标准库中的一个例子是Default trait,它是为可以用默认值初始化的类型来进行实现的。在https://doc.rust-lang.org/std/default/trait.Default.html上有相关的文档。

 

Generic traits

Trait也可以是泛型类别的,这在为各种各样的类型实现一个trait的场景中很有用:

pub trait From<T> {
    fn from(T) -> Self;
}

 

举两个这样的例子:From<T>和Into<T>,这两个可以用作从类型到类型T的转换,反之亦然。当这些trait被用作函数参数的trait边界时,相应的效果就变得尤为突出。稍后我们会看到trait边界是什么,以及它们是如何起作用的。也有个问题,就是当用三到四种泛型类型声明generic trait时,可能会变得非常冗长。对于这些情况,就引出下面要讲的associated type traits。

 

Associated type traits

trait Foo {
    type Out;
    fn get_value(self) -> Self::Out;
}

 

由于能够在trait中声明相关类型,所以associated type traits是generic traits的不错替代,比如上述代码中Foo所声明Out类型,相应类型签名不那么冗长。其优点在于,在实现过程中,允许开发者声明关联的类型一次,并在任何trait方法或函数中使用Self::Out作为返回类型或参数类型。像泛型特征一样,这就消除了有关类型的不必要说明。Associated type traits的一个最好的例子是Iterator trait,它用于迭代自定义类型的值,其文档可以在https://doc.rust-lang.org/std/iter/trait.Iterator.html上找到。到了第8章的高级概念中,我们将更深入的研究Iterator。

 

Inherited traits

我们已经在trait_inheritance.rs中看到了这种trait的例子。与Rust中的类型不同,traits可以有继承关系,例如:

trait Bar {
    fn bar();
}
 
trait Foo: Bar {
    fn foo();
}

在上面的代码片段中,我们声明了一个trait Foo,它依赖于一个超级trait Bar。Foo的定义要求在为类型实现Foo时,也要实现Bar。标准库中的一个例子是Copy trait,它要求类型也实现Clone trait。

 

结语

下一篇开始,我们介绍特性绑定(trait bounds)。

6

+

主要参考和建议读者进一步阅读的文献

https://doc.rust-lang.org/book

Rust编程之道,2019, 张汉东

The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

Beginning Rust ,2018,Carlo Milanesi

Rust Cookbook,2017,Vigneshwer Dhinakaran

上一篇:boost::callable_traits的has_void_return的测试程序


下一篇:Spring笔记:bean的自动装配