为什么rust是安全的?从泛型的角度进一步解析
Table of Contents
如果我没有读过之前的这篇介绍,那么最好还是先学习一下所有权(ownership)吧!
1. 泛型——和C++没有区别
如果我忘记C++的了,推荐阅读: C++的泛型笔记
让我们先来简单过一下rust写泛型的语法,很快的过一遍。
struct Point<T> { x: T, y: T, } fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; } //============================================ struct Point<T, U> { x: T, y: U, } fn main() { let both_integer = Point { x: 5, y: 10 }; let both_float = Point { x: 1.0, y: 4.0 }; let integer_and_float = Point { x: 5, y: 4.0 }; } //============================================ enum Option<T> { Some(T), None, } //============================================ enum Result<T, E> { Ok(T), Err(E), } //============================================ struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); } //==========最复杂的例子===================================== struct Point<T, U> { x: T, y: U, } impl<T, U> Point<T, U> { fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } } fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: "Hello", y: 'c'}; let p3 = p1.mixup(p2); println!("p3.x = {}, p3.y = {}", p3.x, p3.y); }
总结一句话就是:我们认为,泛型可以对类型进行更好地抽象。
2. trait-直译就是:个性
2.1. 我们为什么需要trait?
泛型可能看起来是一个很好的东西,但是这个东西有一个问题: 如果我实例化的类型符合我的泛型,那么泛型代码就可以很好地被执行。如果我实例化的类型里,缺少了某些操作,泛型就会出问题。 上述问题最可怕的地方是: 编译时:我们并不知道我们的泛型适不适合这个类型 。 trait就是为了解决以上问题而生的。
trait就是定义和规定了:这么一些类型可以进行什么样子的操作,有点类似于接口,但是更加强调的是类型的特点,也就是类型支持哪些函数。虽然trait在设计时:一个trait可以包括多个函数,但是一般而言一个trait里可能只有一个函数。
另外一个理解trait的视角是:泛型是一种抽象,而抽象就需要有共性。而trait,就是实现这种共性的基础,也就是基础的共性。比如,加入我要写一个泛型函数,能够对一个向量排序,不管这个向量是整数向量还是字符串向量。那么我们注意到,实现排序需要一个基础操作:比较两个数的大小。如果我没有对整数、字符串中的某一个实现这个基础操作,那么泛型就无法通用。trait捕捉到了这种共同的基础。
我的另外一个理解是:trait挖掘到了实现抽象中的差异。也就是说:trait所要求各个类型实现的,恰恰就是各个类型的特点(你看,这也是trait的意思),也就是各个类型必须自我实现的特点,trait强迫各个类型实现。 再以上面的例子为例,对于实现比较性,整数和字符串有先天的不同,所以我必须实现一个字符串比较的函数,以满足这个trait。
那么可能有一个问题:我能不能不使用trait,直接写泛型?其实是可以的。trait实现了解耦,让泛型写得更加抽象简洁。你可以在泛型里写枚举,实现同样的功能——代码量没有变化。比如上面的例子,我可以判断泛型的类型,如果是字符串,我把字符串排序的方式写上去,就可以了。当然,这在rust里可能行不通。
现在你应该了解了trait了,我们现在来看看怎么用trait约束一个类型。
2.2. 使用trait约束函数或结构体
先定义一个trait。
pub trait Summary { fn summarize(&self) -> String; }
之后,我们定义两个类型,并分别为他们实现这个trait。
#![allow(unused)] fn main() { pub trait Summary { fn summarize(&self) -> String; } 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) } } }
这样我们就可以为这两个类型的对象使用这个trait了。
let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already know, people"), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize());
当然,我们也可以为trait写一个默认的实现。
#![allow(unused)] fn main() { pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } } }
2.3. 把trait当作一种高级类型,以约束函数和泛型
2.3.1. 用trait约束函数
我们的函数过去通过数值的类型去判断输入是否合适,这当然是为了编译的检查。 还有一种方法也可以为编译提供检查,那就是提供trait,因为trait,本身就提供了一个类型空间。从某种程度上来讲,提供trait可以产生更加细粒度的编译检查,同时提供更加广义的函数使用空间。
先看一个例子:
pub fn notify(item: impl Summary) { println!("Breaking news! {}", item.summarize()); }
推荐使用以上格式,如果能用,它实质上是下面的一种简化:
pub fn notify<T: Summary>(item: T) { println!("Breaking news! {}", item.summarize()); }
但如果较为复杂的泛型就简化不了了,如多个类型:
pub fn notify<T: Summary>(item1: T, item2: T) { }
多个trait的话,同样地,也有两种形式:
pub fn notify(item: impl Summary + Display) {} pub fn notify<T: Summary + Display>(item: T) {}
如果是不同类型的,就不能简化了:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 { } //第二种写法 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, } }