今天來聊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中並無嚴格意義上的繼承,可能是用組合的形式。這也體現了「多組合,少繼承」的設計思想。
最後留個預告,這個坑還沒完,咱們下次繼續往深處挖。