Rust入坑指南:海納百川

今天來聊Rust中兩個重要的概念:泛型和trait。很多編程語言都支持泛型,Rust也不例外,相信大家對泛型也都比較熟悉,它可以表示任意一種數據類型。trait同樣不是Rust所特有的特性,它借鑒于Haskell中的Typeclass。簡單來講,Rust中的trait就是對類型行為的抽象,你可以把它理解為Java中的接口。

泛型

在前面的文章中,我們其實已經提及了一些泛型類型。例如Option<T>、Vec<T>和Result<T, E>。泛型可以在函數、數據結構、Enum和方法中進行定義。在Rust中,我們習慣使用T作為通用的類型名稱,當然也可以是其他名稱,只不過習慣上優先使用T(Type)來表示。它可以幫我們消除一些重復代碼,例如實現邏輯相同但參數類型不同的兩個函數,我們就可以通過泛型技術將其進行合并。下面我們分別演示泛型的幾種定義。

在函數中定義

泛型在函數的定義中,可以是參數,也可以是返回值。前提是必須要在函數名的后面加上<T>。

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中定義

在Enum中定義泛型我們已經接觸過比較多了,最常見的例子就是Option<T>和Result<T, E>。其定義方法也和在數據結構中的定義方法類似

enum Result<T, E> {
    Ok(T),
    Err(E),
}

在方法中定義

我們在實現定義了泛型的數據結構或Enum時,方法中也可以定義泛型。例如我們對剛剛定義的Point<T>進行實現。

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

可以看到,我們的方法返回值的類型是T的引用,為了讓編譯器識別T,我們必須要在impl后面加上<T>

另外,我們在對結構體進行實現時,也可以實現指定的類型,這樣就不需要在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

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函數的默認實現。

Trait的實現

上面是一種默認實現,接下來我們介紹一下在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還可以作為函數的參數,也就是需要傳入一個實現了對應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限定列表,這樣可以使代碼的可讀性更高。

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中并沒有嚴格意義上的繼承,多是用組合的形式。這也體現了「多組合,少繼承」的設計思想。

最后留個預告,這個坑還沒完,我們下次繼續往深處挖。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容