【深度知識(shí)】Rust語(yǔ)言入門(mén)、關(guān)鍵技術(shù)與實(shí)戰(zhàn)經(jīng)驗(yàn)

編者按:高可用架構(gòu)分享及傳播在架構(gòu)領(lǐng)域具有典型意義的文章,本文由唐劉在高可用架構(gòu)群分享。轉(zhuǎn)載請(qǐng)注明來(lái)自高可用架構(gòu)公眾號(hào)「 ArchNotes 」。

唐劉,PingCAP 首席架構(gòu)師,現(xiàn)致力于下一代分布式數(shù)據(jù)庫(kù) TiDB、分布式存儲(chǔ) TiKV 的開(kāi)發(fā)。開(kāi)源愛(ài)好者,Go、Rust 等語(yǔ)言愛(ài)好者和實(shí) 踐者。

大家好,我是 PingCAP 的唐劉,今天很榮幸跟大家來(lái)分享一下 Rust 相關(guān)知識(shí)以及我們團(tuán)隊(duì)使用 Rust 的實(shí)戰(zhàn)經(jīng)驗(yàn)。

為什么選擇Rust

首先來(lái)說(shuō)說(shuō)最近 Go 社區(qū)討論比較多的事情,Dropbox 將底層的類(lèi) S3 服務(wù)改用 Rust 來(lái)重寫(xiě)了,一下子讓 Rust 增加了很多知名度,誰(shuí)叫 Dropbox 這種公司通常都是技術(shù)架構(gòu)上面的風(fēng)向標(biāo)。大家普通關(guān)注的一個(gè)問(wèn)題:

為什么 Dropbox 不用 Go,反而用一門(mén)學(xué)習(xí)曲線比較陡峭的 Rust 來(lái)進(jìn)行開(kāi)發(fā)?

其實(shí)這個(gè)問(wèn)題也同樣適用于我們,一個(gè)自認(rèn)為 Go 經(jīng)驗(yàn)非常豐富的團(tuán)隊(duì),為什么不用 Go 反而要選擇 Rust?

介紹一下我們?cè)谧龅氖虑椤N覀儓F(tuán)隊(duì)從事的是下一代分布式數(shù)據(jù)庫(kù)的開(kāi)發(fā),也就是俗稱(chēng)的 NewSQL,整個(gè)理論基礎(chǔ)基于 Google 的 F1 以及 Spanner,所以自然我們的 NewSQL 也是分成了兩塊,一個(gè)是無(wú)狀態(tài)的 SQL 層,也就是現(xiàn)階段已經(jīng)開(kāi)源出來(lái)的 TiDB(http://dwz.cn/2XZkSm),另一個(gè)就是分布式 KV 層,我們叫做 TiKV,預(yù)計(jì)會(huì)在4月份開(kāi)源。

TiDB 是使用 Go 編寫(xiě)的,熟悉 Go 的同學(xué)都應(yīng)該知道,用 Go 來(lái)寫(xiě)分布式應(yīng)用那是非常的快捷方便的,而且我們團(tuán)隊(duì)的成員都有非常深厚的 Go 編程經(jīng)驗(yàn),但是在決定做 TiKV 的時(shí)候,我們沒(méi)有使用 Go,或者使用 C++,Java 這些更流行的靜態(tài)語(yǔ)言,反而是選擇了一門(mén)我們自身完全不熟悉的語(yǔ)言 Rust,Why?

先來(lái)說(shuō)說(shuō)使用 Go 會(huì)遇到的問(wèn)題:

  • GC,雖然 1.6 之后 Go 的 GC 已經(jīng)改善的很好了,而且我們覺(jué)得以后會(huì)越來(lái)越好,但是對(duì)于一個(gè)對(duì)性能要求非常高的分布式應(yīng)用,我們傾向選擇一門(mén)沒(méi)有 GC 的語(yǔ)言,這樣在內(nèi)存上面更加可控。

  • Cgo,TiKV 使用 RocksDB 作為其底層存儲(chǔ) engine,如果用 Go,我們需要使用 Cgo 來(lái)進(jìn)行 RocksDB的調(diào)用,而 Cgo 對(duì)性能還是有比較大的損耗的。

再來(lái)說(shuō)說(shuō)不選擇 C++ 或者 Java,C++ 主要是大規(guī)模開(kāi)發(fā)對(duì)整個(gè)團(tuán)隊(duì)要求非常高,而我們自己也覺(jué)得用 C++ 不可能 hold 住在短時(shí)間內(nèi)做出產(chǎn)品,所以自然放棄。而 Java,我們團(tuán)隊(duì)沒(méi)幾個(gè)人精通 Java,也直接放棄了。

所以,我們最終將目光落到了 Rust 上面,主要在于 Rust 有幾個(gè)很 cool 的 feature,就是 type safety,memory safety 以及 thread safety,這個(gè)后續(xù)詳細(xì)說(shuō)明。

Rust 的基礎(chǔ)入門(mén)

在 Rust 的官網(wǎng)上面,我們可以看到 Rust 的介紹,它是一門(mén)系統(tǒng)編程語(yǔ)言,性能好,同時(shí)在編譯階段就能幫你檢測(cè)出內(nèi)存,多線程數(shù)據(jù)訪問(wèn)等問(wèn)題,保證程序安全健壯。

也就是說(shuō),使用 Rust 寫(xiě)程序,如果能通過(guò)編譯,你就不用擔(dān)心類(lèi)似 C++ 里面很多 memory leak,segment faut,data race 的問(wèn)題了,但這一切都是有代價(jià)的。Rust 上手非常不容易,難度可以跟 C++ 媲美,如果是 Go,沒(méi)準(zhǔn)學(xué)習(xí)一個(gè)星期都能開(kāi)始給項(xiàng)目貢獻(xiàn)代碼,但換成 Rust,可能一個(gè)月都還在跟編譯器作斗爭(zhēng),研究為啥自己的代碼編譯不過(guò)。

因?yàn)槲也磺宄蠹矣卸嗌偃私佑|過(guò) Rust,所以這里列出來(lái)一些例子,讓大家對(duì) Rust 的語(yǔ)法有一個(gè)基本了解。

首先就是最通用的 Hello world:

(點(diǎn)擊圖片可全屏縮放圖片)

fn 是 Rust 的關(guān)鍵字,用來(lái)定義函數(shù),函數(shù)名字是 main, println! 是一個(gè) macro,在 rust style 里面,macro 都是在末尾使用“!”來(lái)表示的。

我們接下來(lái)定義幾個(gè)變量,下面就開(kāi)始顯示出 rust 跟其他語(yǔ)言的不一樣的地方了:

(點(diǎn)擊圖片可全屏縮放圖片)

上面我們聲明了一個(gè) u32 類(lèi)型的變量,但是沒(méi)有初始化,然后打印它的值,在一些語(yǔ)言里面,譬如 Go,會(huì)打印出 0,但是在 Rust 里面,這沒(méi)法編譯通過(guò),編譯器會(huì)提示:

(點(diǎn)擊圖片可全屏縮放圖片)

上面的錯(cuò)誤告訴我們使用了一個(gè)沒(méi)有初始化的變量,我們先來(lái)初始化一下,變成這樣:

(點(diǎn)擊圖片可全屏縮放圖片)

上面我們首先定義了一個(gè)初始值為 0 的變量,然后改成 10 打印,編譯,發(fā)現(xiàn)如下錯(cuò)誤:

(點(diǎn)擊圖片可全屏縮放圖片)

這次編譯器告訴我們對(duì)一個(gè) immutable 的變量進(jìn)行了更改。在 Rust 里面,變量是分為 immutable 和 mutable 的,我們必須顯示地定義這個(gè)變量到底能不能改動(dòng)。我們使用 mut 關(guān)鍵字來(lái)告訴 Rust 這個(gè)變量是 mutable 的,如下:

(點(diǎn)擊圖片可全屏縮放圖片)

這下就能正常編譯了。

通過(guò)上面一些簡(jiǎn)單的例子,大家應(yīng)該對(duì) Rust 有了一個(gè)初步的印象,更加詳細(xì)的了解可以去參考官網(wǎng)。

讓 Rust 開(kāi)發(fā)變 easy 的關(guān)鍵技術(shù)點(diǎn)

前面在為什么選擇 Rust 里面,我提到了因?yàn)?Rust 有幾個(gè)很 cool 的特性,能讓我們寫(xiě)出不容易出錯(cuò)的并發(fā)程序。而且我認(rèn)為,只要理解掌握了這個(gè)關(guān)鍵點(diǎn),用 Rust 進(jìn)行程序開(kāi)發(fā)就很 easy了。

Type safety

Rust 是一門(mén)嚴(yán)格要求類(lèi)型安全的語(yǔ)言,在 C/C++ 的世界里面,我們可以無(wú)拘無(wú)束的進(jìn)行類(lèi)型轉(zhuǎn)換,譬如:

(點(diǎn)擊圖片可全屏縮放圖片)

這種在 C/C++ 里面很常見(jiàn)的處理方式,在 Rust 里面是不被允許的,譬如:

我們會(huì)得到如下編譯錯(cuò)誤:

如果強(qiáng)制做這種內(nèi)存轉(zhuǎn)換,我們可以使用 unsafe,顯示的告訴 Rust 這么做是不安全的,但是你別管了:

(點(diǎn)擊圖片可全屏縮放圖片)

通常情況下面,Rust 是不允許寫(xiě)上面這樣的代碼的,所以它將 unsafe 的選擇權(quán)顯示的交給了程序員,而我們通過(guò) unsafe 也能清晰的知道哪里是不安全的代碼,需要注意的。

Memory safety

下面進(jìn)入 Rust 最令人抓狂的一個(gè)關(guān)鍵點(diǎn)了。Rust 是能夠保證程序的 memory safety 的,那么是如何保證的呢?首先我們需要了解的是 Rust 里面 ownership 以及 move 的概念。

Ownership + move

在Rust里面,任何資源只可能有一個(gè) ownership,譬如一個(gè)最簡(jiǎn)單的例子:

這里我們使用 let 將一個(gè) vector 給綁定到 a 這個(gè)變量上面了,我們就可以認(rèn)為 a 現(xiàn)在是這個(gè) vector 的 ownership。然后對(duì)于這個(gè) vector 的 resouce,同一個(gè)時(shí)間只允許一個(gè) ownership。我們來(lái)看下面這個(gè)代碼:

(點(diǎn)擊圖片可全屏縮放圖片)

在上面的例子中,我們將 a 賦值給了 b,同時(shí)繼續(xù)打印 a[0] 的值,這個(gè)在大多數(shù)語(yǔ)言都沒(méi)有任何問(wèn)題的操作,在 Rust 里面,是會(huì)報(bào)錯(cuò)的,如下:

為什么呢?在打印 a[0] 之前,我們進(jìn)行了 let b = a 這樣的操作,這個(gè)操作,在 Rust 里面叫做 move,含義就是把 a 對(duì) vector 的 ownership 移交給了 b,a 放棄了對(duì) vector 的 ownership。因?yàn)?a 已經(jīng)對(duì)這個(gè) vector 沒(méi)有 ownership 了,自然就不能訪問(wèn)相關(guān)的數(shù)據(jù)了。

ownership 和 move 的概念應(yīng)該算是學(xué)習(xí) Rust 第一個(gè)坑,我們也很容易寫(xiě)出如下代碼:

(點(diǎn)擊圖片可全屏縮放圖片)

這個(gè)代碼照樣是編譯不過(guò)的,因?yàn)?do_vec 這個(gè)函數(shù),a 已經(jīng)將對(duì) vector 的 ownership 給 move 了。

所以通常我們只要看到 let b = a 這樣的代碼,就表明 a move 掉了 ownership 了,但有一個(gè)例外,如果a的類(lèi)型實(shí)現(xiàn)了copy trait,let b =a 就不是move,而是 copy 了,下面的代碼是能正常編譯的:

上面的代碼里面,let b = a,a 并沒(méi)有 move,而是將自己的數(shù)據(jù) copy 了一份給 b 使用。通常基本的數(shù)據(jù)類(lèi)型都是實(shí)現(xiàn)了 copy trait,當(dāng)然我們也可以將我們自定義的類(lèi)型實(shí)現(xiàn) copy,只是就需要權(quán)衡下 copy 的性能問(wèn)題了。

Borrow

前面我們舉了一個(gè) do_vec 的例子,如果真的需要在調(diào)用這個(gè)函數(shù)之后繼續(xù)使用這個(gè) vector,怎么辦呢?

這里我們就開(kāi)始接觸到了第二個(gè)坑,borrow。上面說(shuō)了,move 是我給你了 ownership,而 borrow 則是我借給你這個(gè) resource 的使用權(quán),到時(shí)候還要還給我:

(點(diǎn)擊圖片可全屏縮放圖片)

上面的例子中,我們?cè)趨?shù)里面使用了 & 來(lái)表示 borrow,do_vec 這個(gè)函數(shù)只是借用了 a,然后函數(shù)結(jié)束之后,還了回來(lái),這樣后續(xù)我們就能繼續(xù)使用 a 了。

既然 borrow,當(dāng)然就能用了,于是我們就可能寫(xiě)這樣的代碼:

然后 Rust 又華麗麗的報(bào)錯(cuò)了,輸出:

因?yàn)槲覀兊?borrow 只是 immutable 的 borrow,并不能改數(shù)據(jù)。在前面我們也提到過(guò),如果要對(duì)一個(gè)變量進(jìn)行修改,必須顯示的用 mut 進(jìn)行聲明,borrow 也是一樣,如果要對(duì)一個(gè) borrow 的東西顯示修改,必須使用 mutable borrow,也就是這樣:

(點(diǎn)擊圖片可全屏縮放圖片)

borrow 還有 scope 的概念,有時(shí)候我們寫(xiě)這樣的代碼:

(點(diǎn)擊圖片可全屏縮放圖片)

發(fā)現(xiàn)編譯器又報(bào)錯(cuò)了,輸出:

因?yàn)槲覀冎坝?y 來(lái)對(duì) x 進(jìn)行了mutable 的 borrow,但是還沒(méi)還回去,所以后面 immutable的 borrow 就不允許。這個(gè)我們可以通過(guò) scope 來(lái)顯示的控制 mutable 的生存周期:

(點(diǎn)擊圖片可全屏縮放圖片)

這樣在 printIn! 進(jìn)行 immutable 的 borrow 的時(shí)候,y 這個(gè)mutable 的 borrow 已經(jīng)還回來(lái)了。

一個(gè)變量,可以同時(shí)進(jìn)行多個(gè) immutable 的 borrow,但只允許一個(gè) mutable 的 borrow,這個(gè)其實(shí)跟 read-write lock 很相似,同時(shí)允許多個(gè)讀鎖,但一次只允許一個(gè)寫(xiě)鎖。

Lifetime

在 C++ 里面,相信大家對(duì)野指針都印象深刻,有時(shí)候,我們會(huì)引用了一個(gè)已經(jīng)被 delete 的對(duì)象,然后再次使用的時(shí)候就 panic 了。在 Rust 里面,是通過(guò) lifetime 來(lái)解決這個(gè)問(wèn)題的,不過(guò)引入了 lifetime 之后,代碼看起來(lái)更丑了。一個(gè)簡(jiǎn)單的例子:

(點(diǎn)擊圖片可全屏縮放圖片)

我們定義了一個(gè) struct,里面的 field b 是對(duì)外面一個(gè) u32 變量的引用,而這個(gè)引用的 lifetime是 ’a。使用 lifetime 能保證b 引用的 u32 數(shù)據(jù)的生存周期一定是大于 A 的。

但是在上面的例子中,我們?cè)谝粋€(gè) scope 里面,讓 b 引用了 c,但是 c 在 scope 結(jié)束之后就沒(méi)了,這時(shí)候 b 就是一個(gè)無(wú)效的引用了,所以 Rust 會(huì)編譯報(bào)錯(cuò):

Thread safety

前面我們提到,Rust 使用 move,borrow 以及 lifetime 這些機(jī)制來(lái)保證 memory safety,雖然這幾個(gè)概念不怎么好理解,而且很容易大家寫(xiě)代碼的時(shí)候就會(huì)陷入與編譯器的斗爭(zhēng),但是我個(gè)人覺(jué)得只要理解了這些概念,寫(xiě) Rust 就不是問(wèn)題了。

好了,說(shuō)完了 memory safety,我們馬上進(jìn)入 thread safety了。大家都知道,多線程的程序很難寫(xiě),有且稍微不注意,就會(huì)出現(xiàn) data race 等情況,導(dǎo)致數(shù)據(jù)錯(cuò)誤,而且偏偏這樣的 bug 還能難查出來(lái)。

譬如在 Go 里面,我們可以這樣:

(點(diǎn)擊圖片可全屏縮放圖片)

上面的例子很極端,大家應(yīng)該也不會(huì)這面寫(xiě)代碼,但實(shí)際中,我們?nèi)匀豢赡軙?huì)面臨 data race 的問(wèn)題。雖然 Go 可以通過(guò) --race 打開(kāi) data race 的檢查,可通常只會(huì)用于 test,而不會(huì)在線上使用。

而 Rust 則是在源頭上面完全讓大家沒(méi)法寫(xiě)出 data race 的代碼。首先我們先來(lái)了解 Rust 兩個(gè)針對(duì)并發(fā)的 trait,Send和 Sync:

  • Send

    當(dāng)一個(gè)類(lèi)型實(shí)現(xiàn)了 Send,我們就可以認(rèn)為這個(gè)類(lèi)型可以安全的從一個(gè)線程 move 給另一個(gè)線程去使用。

  • Sync

    當(dāng)一個(gè)類(lèi)型實(shí)現(xiàn)了 Sync,我們就可以認(rèn)為這個(gè)類(lèi)型可以在多線程里面通過(guò) shared reference(也就是 Arc)安全的使用。

上面的概念看起來(lái)比較困惑,簡(jiǎn)單一點(diǎn)就是如果一個(gè)類(lèi)型實(shí)現(xiàn)了Send + Sync,那么這個(gè)就能在多線程下面安全的使用。

先來(lái)看一個(gè)簡(jiǎn)單的例子:

(點(diǎn)擊圖片可全屏縮放圖片)

上面就是一個(gè)典型的多線程 data race 的問(wèn)題了,Rust 是編譯不過(guò)的,報(bào)錯(cuò):

前面說(shuō)了,我們可以通過(guò) Arc 來(lái)保證自己的類(lèi)型在多線程下面安全使用,我們加上 Arc。

(點(diǎn)擊圖片可全屏縮放圖片)

現(xiàn)在我們的 vector 能多線程訪問(wèn)了,但是仍然出錯(cuò):

(點(diǎn)擊圖片可全屏縮放圖片)

因?yàn)槲覀儾还馐且嗑€程 read,而且還需要多線程去 write,自然 Rust 不允許,所以我們需要顯示的進(jìn)行加鎖保護(hù),如下:

(點(diǎn)擊圖片可全屏縮放圖片)

所以,如果我們要對(duì)一個(gè)數(shù)據(jù)進(jìn)行安全的多線程使用,最通用的做法就是使用 Arc<Mutex<T>> 或者 Arc<RwLock<T>>進(jìn)行封裝使用。

當(dāng)然,除了 Arc + Lock 之外,Rust 還提供了 channel 機(jī)制方便進(jìn)行線程之間的數(shù)據(jù)通訊,channel 類(lèi)似于 Go 的 channel,一端 send,一端 recv,這里就不詳細(xì)說(shuō)明了。

這里在提一點(diǎn),因?yàn)樵?Rust 里面,對(duì)于多線程使用的數(shù)據(jù),我們必須明確的進(jìn)行 lock 保護(hù),這個(gè)編程風(fēng)格直接帶到了后來(lái)我們寫(xiě) Go 。在 Go 里面,我以前寫(xiě) lock,通常會(huì)這樣:

(點(diǎn)擊圖片可全屏縮放圖片)

使用一個(gè) mutex 變量 m 來(lái)對(duì)數(shù)據(jù) v1,v2 進(jìn)行多線程保護(hù),但這種寫(xiě)法其實(shí)很容易就容易忘記這個(gè) lock 到底要保護(hù)哪些數(shù)據(jù)。自從受 Rust 影響了之后,我就喜歡寫(xiě)成這樣了:

(點(diǎn)擊圖片可全屏縮放圖片)

上面就是顯示的將 lock 以及需要保護(hù)的數(shù)據(jù)放到一個(gè) struct 里面,大家一看代碼就知道這個(gè) lock 要保護(hù)哪些數(shù)據(jù)了。

Rust 開(kāi)發(fā)實(shí)戰(zhàn)經(jīng)驗(yàn)

前面說(shuō)了是 Rust 的一些基本 feature,這里開(kāi)始說(shuō)下我們項(xiàng)目中用 Rust 的相關(guān)經(jīng)驗(yàn)。

Cargo

如果要用 Rust進(jìn)行項(xiàng)目開(kāi)發(fā),首先就需要了解的就是 Cargo,Cargo 是 Rust 一個(gè)構(gòu)建以及包管理工具,現(xiàn)在應(yīng)該已經(jīng)成了 Rust 開(kāi)發(fā)項(xiàng)目的規(guī)范了。Cargo 的使用還是很簡(jiǎn)單的,大家可以直接去看瀏覽官網(wǎng) (https://crates.io/)。

quick_error!

最開(kāi)始,我們?cè)趯?xiě) C 程序的時(shí)候,通過(guò)定義不同的 int 返回值來(lái)表示一個(gè)函數(shù)是不是有 error。然后到了 C++,我們就可以通過(guò) expection 來(lái)處理 error 了。不過(guò)到底采用哪種標(biāo)準(zhǔn),都是沒(méi)有定論的。

到了 Go,直接約定了函數(shù)最后一個(gè)返回參數(shù)是 error,官方還有一篇 blog 來(lái)介紹了 Go 的error handling (http://blog.golang.org/error-handling-and-go)。

在 Rust 里面,error 也有相應(yīng)的處理規(guī)范,就是 Result,Result 是一個(gè) enum,定義是這樣的:

(點(diǎn)擊圖片可全屏縮放圖片)

也就是說(shuō),我們的函數(shù)都可以返回 Result,外面去判斷,如果是 Ok,那么就是正確的處理,如果是 Err 則是錯(cuò)誤了。

這里有篇 error handling 的詳細(xì)說(shuō)明 (https://doc.rust-lang.org/book/error-handling.html)。

通常大家都會(huì)按照上面的規(guī)范來(lái)處理 error,也就是定義自己的 error,實(shí)現(xiàn)其他 error 轉(zhuǎn)成自己 error 的 from 函數(shù),然后在使用 try! 在代碼里面簡(jiǎn)化 error 的處理。

但是很快我們就發(fā)現(xiàn)了一個(gè)很?chē)?yán)重的問(wèn)題,定義自己的 error,以及將其他 error 轉(zhuǎn)成我們對(duì)應(yīng)的 error 是一件非常冗余復(fù)雜的事情,所以我們使用 quick_error!(http://dwz.cn/2XZpNo)來(lái)簡(jiǎn)化整個(gè)流程。

Clippy

當(dāng)我第一次寫(xiě) Go 代碼的時(shí)候,我對(duì) Go 的 fmt 印象特別深刻,以后再也不用擔(dān)心編碼風(fēng)格的爭(zhēng)論了,Rust 也有相關(guān)的 rust fmt,但是更令我驚奇的是 Rust 的 Clippy 這個(gè)工具。Clippy 已經(jīng)不是糾結(jié)于編碼風(fēng)格了,而是直接告訴你代碼要這么寫(xiě),那么寫(xiě)不對(duì)。

一個(gè)很簡(jiǎn)單的例子:

(點(diǎn)擊圖片可全屏縮放圖片)

這個(gè)代碼是能編譯通過(guò)的,但是如果我們打開(kāi)了 Clippy 的支持,直接會(huì)提示:

(點(diǎn)擊圖片可全屏縮放圖片)

也就是告訴你,別用 to_string,用 to_owned。

我們都知道,要開(kāi)發(fā)一個(gè)高性能的網(wǎng)絡(luò)服務(wù),通常的選擇就是 epoll 這種基于事件觸發(fā)的網(wǎng)絡(luò)模型,在 Rust,現(xiàn)階段成熟的庫(kù)就是 MIO。MIO是一個(gè)異步 IO 庫(kù),對(duì)不同的操作系統(tǒng)提供了統(tǒng)一抽象支持,譬如 Linux 下面就是 epoll,UNIX 下面就是 kqueue,Windows 下是 IOCP。不過(guò) MIO 為了統(tǒng)一擴(kuò)平臺(tái),在一些實(shí)現(xiàn)上面做了妥協(xié)。

MIO

譬如在 Linux 下面,系統(tǒng)直接提供了 event fd 的支持,但 MIO 為了兼容 UNIX,使用了傳統(tǒng)的 pipe 而不是 event fd 來(lái)進(jìn)行 event loop 的 awake 處理。

這里在單獨(dú)說(shuō)下 MIO 提供的另一種線程通訊 channel 機(jī)制,雖然我們可以用 Rust 自己的 thread channel 來(lái)進(jìn)行線程通訊,但如果引入 MIO,我更喜歡用 MIO 自己的 channel,主要原因是采用的 lock free queue,性能更好,但有 queue size 限制問(wèn)題,發(fā)送太頻繁但接受端沒(méi)處理過(guò)來(lái),就會(huì)造成發(fā)送失敗的問(wèn)題了。

Rust 語(yǔ)言的美中不足

我們團(tuán)隊(duì)已經(jīng)使用 Rust進(jìn)行了幾個(gè)月的開(kāi)發(fā),當(dāng)然也遇到了一些很不爽的地方。

首先就是庫(kù)的不完善,相比于 Go,Rust 的庫(kù)真的太不完備了。我覺(jué)得現(xiàn)階段 Rust 仍然沒(méi)有大規(guī)模的應(yīng)用,lib 不完備占了很大一個(gè)原因。

TiKV 是一個(gè)服務(wù)器程序,自然就會(huì)涉及到網(wǎng)絡(luò)編程,官方現(xiàn)階段的 net mod 里面,只有 block socket 的支持,這個(gè)完全沒(méi)法用來(lái)開(kāi)發(fā)高性能網(wǎng)絡(luò)程序的。幸好有 MIO,但光有 MIO 是遠(yuǎn)遠(yuǎn)不夠,在 Go 里面,我們很方便的使用 gRPC 來(lái)進(jìn)行 RPC 的編寫(xiě),但是在 Rust 里面,我覺(jué)得還得在等很長(zhǎng)一段時(shí)間,看能不能有開(kāi)源的實(shí)現(xiàn)。

再來(lái)就是在 Mac OS X 下面,panic 出來(lái)的堆棧完全沒(méi)法看,沒(méi)有 file 和 line number 的信息,根本沒(méi)法方便的查 bug。

當(dāng)然,畢竟 Rust 是一門(mén)比較新的語(yǔ)言,還在不斷的完善發(fā)展,我們還是很有信心它能越來(lái)越好的。

Q & A

1. Go 的 Cgo 在效率上面與 Rust FFI 有啥區(qū)別?

唐劉:我自己寫(xiě)過(guò)一個(gè)簡(jiǎn)單的測(cè)試,就是都循環(huán)調(diào)用 Snappy 的 MaxCompressedLength 這個(gè)函數(shù) 10000 次,發(fā)現(xiàn) Rust 的 FFI 比 Go 的 Cgo 要快上一個(gè)數(shù)量級(jí),雖然這么測(cè)試不怎么精確,但至少證明了 Rust 的 FFI 性能更好。

2. 在官方的介紹中,Rust 的首選平臺(tái)是 Windows, 那是否可以生成 dll. 你們的 IDE 用的是什么?

唐劉:我沒(méi)用過(guò) Windows,所以也不知道怎么生成 dll,開(kāi)發(fā) Rust 的 ide 也就是常用的那幾個(gè),譬如 Vim, Emacs, Sublime 這些,反正都有 Rust 的插件支持。

3. Rust 調(diào)用 C 的庫(kù)方便嗎?

唐劉:Rust 通過(guò) FFI 調(diào)用 C,很方便的,這里有相關(guān)文檔 (https://doc.rust-lang.org/book/ffi.html),但畢竟這涉及到跨語(yǔ)言,代碼寫(xiě)起來(lái)就不怎么好看了。而且 FFI 需要 unsafe保護(hù),所以通常我們會(huì)在外面在 wrap 一層 Rust 的函數(shù)。

4. Rust 性能指標(biāo)如何?

唐劉:Rust 性能這個(gè)不怎么好衡量,因?yàn)槲覀冎皇窃谝恍┨囟ǖ沫h(huán)境下面做過(guò)跟 Go 的對(duì)比,譬如 Cgo vs FFI 的測(cè)試,這方面性能是比 Go 要好的。另外,Rust 是一門(mén)靜態(tài)語(yǔ)言,沒(méi)有 GC,所以我覺(jué)得他的性能不會(huì)是問(wèn)題,不然 Dropbox 也不可能將類(lèi) S3 的應(yīng)用用 Rust 寫(xiě)了。

5. Rust 周邊生態(tài)如何? 如跟常用的 DBSQL/MQ 等第三方系統(tǒng)的 binding?

唐劉:Rust 的生態(tài)只能呵呵來(lái)形容了,跟 Go,Java 這些的沒(méi)法比。雖然有常用的 MySQL 等的 binding,但我沒(méi)用過(guò)。主要原因在于官方的網(wǎng)絡(luò) IO 是同步的,所以必須借助多線程來(lái)進(jìn)行這些處理,而用 MIO 這種異步模式,大家也知道寫(xiě)出來(lái)的代碼邏輯切割很厲害。所以通常我們也不會(huì)拿 Rust 來(lái)做這些復(fù)雜的業(yè)務(wù)系統(tǒng)開(kāi)發(fā),感覺(jué)還是 Go 更合適。

與C,C++不同,java規(guī)范中沒(méi)有“依賴(lài)具體實(shí)現(xiàn)”的地方,基本數(shù)據(jù)類(lèi)型大小以及有關(guān)算法都做了明確的說(shuō)明。例如,Java中int類(lèi)型永遠(yuǎn)為32位整數(shù),而C/C++中int的類(lèi)型大小有可能是16位,32位,也可能與編譯器的設(shè)置有關(guān)。在java中,數(shù)據(jù)類(lèi)型具有固定的大小,從而具有很好的可移植性。

本文轉(zhuǎn)載自 高可用架構(gòu)公眾號(hào)「 ArchNotes 」文章
原文:《Rust語(yǔ)言入門(mén)、關(guān)鍵技術(shù)與實(shí)戰(zhàn)經(jīng)驗(yàn)》
作者: 唐劉
鏈接:https://mp.weixin.qq.com/s/Wlz1G-eYgA9uEgO_63lK6Q


參考

(1)Rust 語(yǔ)言中文版-極客學(xué)院教程
http://wiki.jikexueyuan.com/project/rust/
(2)RUST官方網(wǎng)站
https://www.rust-lang.org/zh-CN/
https://www.rust-lang.org/zh-CN/learn
(3)標(biāo)準(zhǔn)文檔庫(kù)
https://doc.rust-lang.org/stable/std/index.html

補(bǔ)充

(1)GO和RUST的區(qū)別?
  1. go定位是云計(jì)算時(shí)代的C語(yǔ)言,主要是面向云計(jì)算領(lǐng)域的。而rust是系統(tǒng)級(jí)語(yǔ)言,更Low level一些
  2. 都是強(qiáng)類(lèi)型語(yǔ)言,go有GC(Garbage Collection,垃圾回收)而rust沒(méi)有;
    3)go目前沒(méi)有模板,rust有,換言之,rust的編程范式更豐富一些
  3. go簡(jiǎn)單,上手快,rust因?yàn)橛凶兞康膌ifetime概念和內(nèi)存的borrow概念,上手難一些;
    5)安全性。Rust語(yǔ)法引入所有權(quán)和生命期概念,在編譯期就能檢查出一部分內(nèi)存管理錯(cuò)誤,這是rust的一個(gè)殺手锏的特性。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評(píng)論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,980評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 64,064評(píng)論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評(píng)論 6 414
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,109評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,287評(píng)論 0 291
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,327評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,667評(píng)論 1 296
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,492評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容