今天来聊Rust中两个重要的概念:泛型和trait。不少编程语言都支持泛型,Rust也不例外,相信你们对泛型也都比较熟悉,它能够表示任意一种数据类型。trait一样不是Rust所特有的特性,它借鉴于Haskell中的Typeclass。简单来说,Rust中的trait就是对类型行为的抽象,你能够把它理解为Java中的接口。 编程
在前面的文章中,咱们其实已经说起了一些泛型类型。例如Option
泛型在函数的定义中,能够是参数,也能够是返回值。前提是必需要在函数名的后面加上
fn largest<T>(list: &[T]) -> T {
若是数据结构中某个字段能够接收任意数据类型,那么咱们能够把这个字段的类型定义为T,一样的,为了让编译器认识这个T,咱们须要在结构体名称后边标识一下。函数
struct Point<T> { x: T, y: T, }
上面的例子中,x和y都是能够接受任意类型,可是,它们两个的类型必须相同,若是传入的类型不一样,编译器仍然会报错。那若是想要让x和y可以接受不一样的类型应该怎么办呢?其实也很简单,咱们定义两种不一样的泛型就行了。性能
struct Point<T, U> { x: T, y: U, }
在Enum中定义泛型咱们已经接触过比较多了,最多见的例子就是Option
enum Result<T, E> { Ok(T), Err(E), }
咱们在实现定义了泛型的数据结构或Enum时,方法中也能够定义泛型。例如咱们对刚刚定义的Point
impl<T> Point<T> { fn x(&self) -> &T { &self.x } }
能够看到,咱们的方法返回值的类型是T的引用,为了让编译器识别T,咱们必需要在impl
后面加上<T>
。orm
另外,咱们在对结构体进行实现时,也能够实现指定的类型,这样就不须要在impl
后面加标识了。继承
impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
了解了泛型的几种定义以后,你有没有想过一个问题:Rust中使用泛型会对程序运行时的性能形成不良影响吗?答案是不会,由于Rust对于泛型的处理都是在编译阶段进行的,对于咱们定义的泛型,Rust编译器会对其进行单一化处理,也就是说,咱们定义一个具备泛型的函数(或者其余什么的),Rust会根据须要将其编译为具备具体类型的函数。接口
let integer = Some(5); let float = Some(5.0);
例如咱们的代码使用了这两种类型的Option,那么Rust编译器就会在编译阶段生成两个指定具体类型的Option。
enum Option_i32 { Some(i32), None, } enum Option_f64 { Some(f64), None, }
这样咱们在运行阶段直接使用对应的Option就能够了,而不须要再进行额外复杂的操做。因此,若是咱们泛型定义并使用的范围很大,也不会对运行时性能形成影响,受影响的只有编译后程序包的大小。
Trait能够说是Rust的灵魂,Rust中全部的抽象都是依靠Trait来实现的。
咱们先来看看如何定义一个Trait。
pub trait Summary { fn summarize(&self) -> String; }
定义trait使用了关键字trait
,后面跟着trait的名称。其内容是trait的「行为」,也就是一个个函数。可是这里的函数没有实现,而是直接以;
结尾。不过这这并非必须的,Rust也支持下面这种写法:
pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } }
对于这样的写法,它表示summarize函数的默认实现。
上面是一种默认实现,接下来咱们介绍一下在Rust中,对一个Trait的常规实现。Trait的实现是须要针对结构体的,即咱们要写明是哪一个结构体的哪一种行为。
pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } } pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } }
上述代码中,咱们分别定义告终构体NewArticle和Tweet,而后为它们实现了trait,定义了summarize函数对应的逻辑。
此外,trait还能够做为函数的参数,也就是须要传入一个实现了对应trait的结构体的实例。
pub fn notify(item: impl Summary) { println!("Breaking news! {}", item.summarize()); }
做参数时,咱们须要使用impl
关键字来定义参数类型。
Rust还提供了另外一种语法糖来,即Trait限定,咱们可使用泛型约束的语法来限定Trait参数。
pub fn notify<T: Summary>(item: T) { println!("Breaking news! {}", item.summarize()); }
如上述代码,咱们能够经过Trait来限定泛型T的范围。这样的语法糖能够在多个参数的函数中帮助咱们简化代码。下面两行代码就有比较明显的对比
pub fn notify(item1: impl Summary, item2: impl Summary) { pub fn notify<T: Summary>(item1: T, item2: T) {
若是某个参数有多个trait限定,就可使用+
来表示
pub fn notify<T: Summary + Display>(item: T) {
若是咱们有更多的参数,而且有每一个参数都有多个trait限定,及时咱们使用了上面这种语法糖,代码仍然有些繁杂,会下降可读性。因此Rust又为咱们提供了where
关键字。
fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug {
它帮助咱们在函数定义的最后写一个trait限定列表,这样可使代码的可读性更高。
fn returns_summarizable() -> impl Summary { Tweet { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already know, people"), reply: false, retweet: false, } }
Trait做为返回值类型,和做为参数相似,只须要在定义返回类型时使用impl Trait
。
本文咱们简单介绍了泛型和Trait,包括它们的定义和使用方法。泛型主要是针对数据类型的一种抽象,而Trait则是对数据类型行为的一种抽象,Rust中并无严格意义上的继承,可能是用组合的形式。这也体现了「多组合,少继承」的设计思想。
最后留个预告,这个坑还没完,咱们下次继续往深处挖。