行研報告:三萬字讀懂Rust語言
本文來自本文來自本文來自
本文來自
,作者張漢東,經授權轉載。
文前
內容目錄:
認識Rust 語言
可靠性
媲美C / Cpp 的高性能
可靠性
生產力
Rust 與開源
Rust 語言的不足
Rust 生態基礎庫和工具鏈
Rust 行業應用盤點
數據服務
機器學習
遊戲
遊戲
遊戲
遊戲
盤點在生產環境使用Rust 的公司
國內
國內
npm
mdnice編輯器
mdnice編輯器
文前
mdnice編輯器
文前Rust 語言是一門通用系統級編程語言,無GC且能保證內存安全、並發安全和高性能而著稱。自2008年開始由Graydon Hoare 私人研發,2009年得到Mozilla 贊助,2010年首次發布0.1.0 版本,用於Servo 引擎的研發,於2015年5月15號發布1.0 版本。
自發布以來,截止到2021 年的今天,經歷六年的發展,Rust 得到穩步上升,已逐漸趨於成熟穩定。
至2016 年開始,截止到2021年,Rust 連續五年成為
StackOverflow 語言榜上最受歡迎的語言[1]。
註明:本文中所羅列數據均來源互聯網公開內容。
mdnice編輯器
mdnice編輯器
mdnice編輯器
認識Rust 語言
編程語言設計在兩個看似不可調和的願望之間長期存在著矛盾對立。
安全( safe )。我們想要強類型系統來靜態地消除大量錯誤。我們要自動內存管理。我們想要數據封裝, 這樣我們就可以對私有變量執行不變的對象的表示形式,並確保它們將不會被不受信任的代碼破壞。
控制(control )。至少對於Web瀏覽器,操作系統,或遊戲引擎這樣的系統編程(system programming) 程序,約束它們性能或資源是一個重要的問題,我們想了解數據的字節級表示。我們想要用底層語言(low-level programming) 的編程技術優化我們程序的時間和空間的使用。我們希望在需要時使用裸機。
然而,按照傳統的看法,魚和熊掌不能兼得。 Java 之類的語言使我們極大的安全保證,但代價是犧牲對底層的控制。結果,對於許多系統編程應用程序,唯一現實的選擇是使用一種像C 或C++ 提供細粒度的語言控制資源管理。但是,獲得這種控制需要很高的成本。例如,微軟最近報告說,他們修復的70% 安全漏洞都歸因於內存安全違規行為33[2],並且都是能被強類型系統排除的問題。同樣,Mozilla 報告指出,絕大多數關鍵他們在Firefox中發現的錯誤是內存有關的16 [3]。
如果可以以某種方式兩全其美: 安全系統編程的同時對底層有控制權,豈不美哉。因此,Rust 語言應運而生。
官方網如此介紹Rust : 一門賦予每個人構建可靠且高效軟件能力的語言。
Rust 語言有三大優勢值得大家關注:
高性能。 Rust 速度驚人且內存利用率極高。由於沒有運行時和垃圾回收,它能夠勝任對性能要求特別高的服務,可以在嵌入式設備上運行,還能輕鬆和其他語言集成。
可靠性。 Rust 豐富的類型系統和所有權模型保證了內存安全和線程安全,讓您在編譯期就能夠消除各種各樣的錯誤。
生產力。 Rust 擁有出色的文檔、友好的編譯器和清晰的錯誤提示信息, 還集成了一流的工具——包管理器和構建工具, 智能地自動補全和類型檢驗的多編輯器支持, 以及自動格式化代碼等等。
Rust 足夠底層,如果有必要,它可以像C 一樣進行優化,以實現最高性能。
Rust 語言也支持高並發零成本的異步編程,Rust 語言應該是首個支持異步編程的系統級語言。
mdnice編輯器
Rust vs C
Rust vs Cpp
Rust vs Go
mdnice編輯器
mdnice編輯器
媲美C / Cpp 的高性能
用Rust 編寫的程序的運行時速度和內存使用量應該和用C 編寫的程序差不多,但這兩種語言的總體編程風格不同,很難去概括它們的性能。
總的來說:
抽像是一把雙刃劍。 Rust 語言抽象程度比C 語言更高,抽象會隱藏一些不是那麼優化的代碼,這意味著,默認實現的Rust 代碼性能不是最好的。所以,你的Rust 代碼必須經過優化才能達到媲美C 的性能。 Unsafe Rust 就是高性能出口。
Rust 默認線程安全,消除數據競爭,讓多線程並發編程更具實用價值。
Rust 在有些方面確實比C 快。理論上,C 語言什麼都可以做。但在實踐中,C 抽象能力比較低,不那麼現代化,開發效率比較低。只要開發者有無限時間和精力,就可以讓C 語言在這些方面比Rust 更快。
因為C 語言足以代表高性能,下面就分別談一下C 和Rust 的異同。如果你熟悉C/Cpp,也可以根據此對比來評估Cpp 和Rust。"Rust 和C 都是直接對硬件的抽象,都可看作一種「可移植彙編程序」。"。
魔法"魔法")。
零成本抽象
零成本抽象
Rust的類型的內存佈局很簡單,例如,可增長的字符串String 和Vec
正好是{byte*, capacity, length}。 Rust沒有任何像Cpp裡的移動或複制構造函數這樣的概念,所以對象的傳遞保證不會比傳遞指針或memcpy 更複雜。
Rust 借用檢查只是編譯器對代碼中引用的靜態分析。生命週期(lifetime)信息早就在中級中間語言(MIR) 生成前完全抽離了。
Rust 中不使用傳統的異常處理,而是使用基於返回值的錯誤處理。但你也可以使用恐慌(Panic)來處理像Cpp 中那樣的異常行為。它可以在編譯時被禁用(panic = abort),但即便如此,Rust 也不喜歡與Cpp異常或longjmp 混在一起。
同樣的LLVM 後端
Rust與LLVM有很好的整合,所以它支持鏈接時間優化,包括ThinLTO,甚至是跨越C/C++/Rust語言邊界的內聯。也有按配置優化(Profile-guided Optimization,PGO)的支持。儘管rustc 比clang 生成的LLVM IR更加冗長,但優化器仍然能夠很好地處理它。
C 語言用GCC 編譯比用LLVM 更快,現在Rust 社區也有人在開發GCC 的Rust 前端。
理論上,因為Rust 有比C 更嚴格的不可變和別名規則,應該比C 語言有更好的性能優化,但實際上並沒有起到這樣的效果。目前在LLVM中,超越C語言的優化是一項正在進行的工作,所以Rust仍然沒有達到其全部潛力。
都允許手工優化,但有一些小例外
Rust 的代碼足夠底層和可預測,可以通過手工調整它的優化到什麼樣的彙編代碼。
Rust 支持SIMD ,對內聯和調用約定有很好的控制。
Rust 和C 語言足夠相似,C 語言的一些分析工具通常可以用於Rust 。
總的來說,如果性能絕對關鍵,並且需要手工優化壓榨最後一點性能,那麼優化Rus t 與優化C 語言並沒有什麼不同。
但是在一些比較底層的特性,Rust 沒有特別好的替代方法。
goto。 Rust 中沒有提供goto,不過你可以使用循環的break 標籤來代替。 C 語言中一般使用goto 來清理內存,但是Rust 因為有確定性析構功能,所以不需要goto。然而有一個非標準的goto 擴展,對性能優化比較有用。
棧內存分配alloca和C99可變長度數組,可以節省內存空間,減少內存分配次數。但這些即使在C 語言中也是有爭議的,所以Rust遠離了它們。
Rust 相比C 語言的一些開銷
如果沒有經過手工優化,Rust 因為其抽象表達也會有一些開銷。
Rust缺乏隱式類型轉換和只用usize的索引,這導致開發者只能使用這種類型,哪怕只需要更小的數據類型。 64位平台上用usize做索引更容易優化,而不需要擔心未定義行為,但多餘的bit位可能會給寄存器和內存帶來更大的壓力。而在C 中,你可以選擇32位類型。
Rust 中的字符串,總是會攜帶指針和長度。但是很多C 代碼中的函數只接收指針而不管大小。
像for i in 0...len {arr[i]} 這樣的迭代,性能取決於LLVM 優化器能否證明長度匹配。有時候,它不能,並且邊界檢查也會抑制自動矢量化。
C 語言比較自由,對於內存有很多“聰明”的使用技巧,但在Rust 裡就沒這麼自由了。但Rust仍然給了內存分配很多控制權,並且可以做一些基本的事情,比如內存池、將多個分配合併為一個、預分配空間等等。
在不熟悉Rust 借用檢查的情況下,可能會用Clone 來逃避使用引用。
Rust 的標準庫中I/O 是不帶緩存的,所以需要使用BufWriter 來包裝。這就是為什麼有些人說Rust 寫的代碼還不如Python 快的原因,因為99% 的時間都用在I/O上了。"Hello World "可執行文件大小
每個操作系統都有一些內置的標準C庫,其中有大約30MB的代碼。 C 語言的執行文件,可以“免費”使用這些庫。
一個小的"no-std"級C 可執行文件實際上不能打印任何東西,它只調用操作系統提供的printf。"而Rust 則不可以,Rust可執行文件會捆綁自己的標準庫(300KB或更多)。幸運的是,這只是一次性的開銷,可以減少。"對於嵌入式開發,可以關閉標準庫,使用
,Rust將生成"裸"代碼。
在每個函數的基礎上,Rust代碼的大小與C差不多,但有一個
泛型膨脹
的問題。泛型函數為它們所使用的每一種類型都有優化的版本,所以有可能出現同一個函數有8個版本的情況,cargo-bloat[4] 庫有助於發現這些問題。
在Rust中使用依賴關係是非常容易的。與JS/npm 類似,現在推薦使用小型且單用途的包,但它們確實在不斷增加。 cargo-tree 命令對於刪減它們非常有用。
Rust 略勝C 的一些地方
為了隱藏實現細節,C 庫經常返回不透明的數據結構指針,並確保結構的每個實例只有一個副本。它會消耗堆分配和指針間接尋址的成本。 Rust 內置的隱私、單一所有權規則和編碼慣例允許庫暴露其對象,而不需要間接性,這樣,調用者可以決定將其放入堆(heap)上還是棧(stack)中。可以主動或徹底地優化棧上的對象。
缺省情況下,Rust 可以將來自標準庫、依賴項和其他編譯單元的函數內聯。
Rust 會對結構體字段進行重排,以優化內存佈局。
字符串攜帶大小信息,使得長度檢查速度很快。並允許就地生成子串。
與C++ 模板類似,Rust 中泛型函數會單態化,生成不同類型的副本,因此像sort 這樣的函數和HashMap 這樣的容器總是針對相應的類型進行優化。對於C 語言,則必須在修改宏或者處理void*和運行時變量大小的效率較低的函數之間做出選擇。
Rust的迭代器可以組合成鏈狀,作為一個單元一起被優化。因此,你可以調用it.buy().use().break().change().mail().upgrade(),而不是對同一個緩存區多次寫入的一系列調用。
同樣,通過Read 和Write 接口,接收一些未緩存的流數據,在流中執行CRC 校驗,然後將其轉碼、壓縮,再寫入網絡中,所有這些都可以在一次調用中完成。雖然C 語言中應該也可以做到,但它沒有泛型和特質(trait),將很難做到。
Rust 標準庫中內置高質量的容器和優化過的數據結構,比C 使用起來更方便。
Rust的serde 是世界上最快的JSON解析器之一,使用體驗非常棒。
Rust 比C 明顯優越的地方
主要是兩點:
Rust 消除數據競爭,天生線程安全,解放多線程生產力,是Rust 明顯比C / Cpp 等語言優越的地方。
Rust 語言支持異步高並發編程。
Rust 支持安全的編譯期計算。
線程安全
即使是在第三方庫中,Rust 也會強制實現所有代碼和數據的線程安全,哪怕那些代碼的作者沒有註意線程安全。一切都遵循一個特定的線程安全保證,或者不允許跨線程使用。當你編寫的代碼不符合線程安全時,編譯器會準確地指出不安全之處。
Rust 生態中已經有了很多庫,如數據並行、線程池、隊列、任務、無鎖數據結構等。有了這類組件的幫助,再加上類型系統強大的安全網,完全可以很輕鬆地實現並發/並行化Rust 程序。有些情況下,用par_iter 代替iter 是可以的,只要能夠進行編譯,就可以正常工作!這並不總是線性加速( 阿姆達爾定律(Amdahl's law)很殘酷),但往往是相對較少的工作就能加速2~3 倍。
延伸:阿姆達爾定律,一個計算機科學界的經驗法則,因Gene Amdahl 而得名。它代表了處理器並行計算之後效率提升的能力。
在記錄線程安全方面,Rust 和C 有一個有趣的不同。
Rust 有一個術語表用於描述線程安全的特定方面,如Send 和Sync、guards 和cell。
對於C 庫,沒有這樣的說法:“可以在一個線程上分配它,在另一個線程上釋放它,但不能同時從兩個線程中使用它”。
根據數據類型,Rust 描述了線程安全性,它可以泛化到所有使用它們的函數。
對於C 語言來說,線程安全只涉及單個函數和配置標誌。
Rust 的保證通常是在編譯時提供的,至少是無條件的。
對於C 語言,常見的是“僅當turboblub 選項設置為7 時,這才是線程安全的”。
異步並發
Rust 語言支持async/await異步編程模型。
該編程模型,基於一個叫做Future 的概念,,在JavaScript 中也叫做Promise。 Future 表示一個尚未得出的值,你可以在它被解決(resolved)以得出那個值之前對它進行各種操作。在許多語言中,對Future 所做的工作並不多,這種實現支持很多特性比如組合器(Combinator),尤其是能在此基礎上實現更符合人體工程學的async/await 語法。
Future 可以表示各種各樣的東西,尤其適用於表示異步I/O :當你發起一次網絡請求時,你將立即獲得一個Future 對象,而一旦網絡請求完成,它將返回任何響應可能包含的值;你也可以表示諸如“超時”之類的東西,“超時”其實就是一個在過了特定時間後被解決的Future ;甚至不屬於I/O 的工作或者需要放到某個線程池中運行的CPU密集型的工作,也可以通過一個Future 來表示,這個Future 將會在線程池完成工作後被解決。
Future 存在的問題是它在大多數語言中的表示方式是這種基於回調的方法,使用這種方式時,你可以指定在Future 被解決之後運行什麼回調函數。也就是說, Future 負責弄清楚什麼時候被解決,無論你的回調是什麼,它都會運行;而所有的不便也都建立在此模型上,它非常難用,因為已經有很多開發者進行了大量的嘗試,發現他們不得不寫很多分配性的代碼以及使用動態派發;實際上,你嘗試調度的每個回調都必須獲得自己獨立的存儲空間,例如crate 對象、堆內存分配,這些分配以及動態派發無處不在。這種方法沒有滿足零成本抽象的第二個原則,如果你要使用它,它將比你自己寫要慢很多,那你為什麼還要用它。
Rust 中的方案有所不同。不是由Future 來調度回調函數,而是由一個被稱為執行器(executor)的組件去輪詢Future。而Future 可能返回“尚未準備就緒(Pending)”,也可能被解決就返回“已就緒(Ready)”。該模型有很多優點。其中一個優點是,你可以非常容易地取消Future ,因為取消Future 只需要停止持有Future。而如果採用基於回調的方法,要通過調度來取消並使其停止就沒這麼容易了。
同時它還能夠使我們在程序的不同部分之間建立真正清晰的抽象邊界,大多數其他Future 庫都帶有事件循環(event loop),這也是調度你的Future 執行I/O 的方法,但實際上你對此沒有任何控制權。
而在Rust 中,各組件之間的邊界非常整潔,執行器(executor)負責調度你的Future ,反應器(reactor)處理所有的I/O ,然後是你的實際代碼。因此最終用戶可以自行決定使用什麼執行器,使用他們想使用的反應器,從而獲得更強的控制力,這在系統編程語言中真的很重要。
而此模型最重要的真正優勢在於,它使我們能夠以一種真正零成本的完美方式實現這種狀態機式的Future 。也就是當你編寫的Future 代碼被編譯成實際的本地(native)代碼時,它就像一個狀態機;在該狀態機中,每次I/O 的暫停點都有一個變體(variant),而每個變體都保存了恢復執行所需的狀態。
而這種Future 抽象的真正有用之處在於,我們可以在其之上構建其他API 。可以通過將這些組合器方法應用於Future 來構建狀態機,它們的工作方式類似於迭代器(Iterator)的適配器(如filter、map)。但是這種方式是有一些缺點的,尤其是諸如嵌套回調之類,可讀性非常差。所以才需要實現async / await異步語法。
目前Rust 生態中,已經有了成熟的tokio[5] 運行時生態,支持epoll 等異步I/O。如果你想用io_uring ,也可以使用Glommio[6] ,或者等待tokio 對io_uring 的支持。甚至,你可以使用smol 運行時提供的async_executor[7] 和async-io[8] 來構建你自己的運行時。
mdnice編輯器
mdnice編輯器
可靠性
mdnice編輯器
可靠性
2020 年6月份,來自3所大學的5位學者在ACM SIGPLAN國際會議(PLDI'20)上發表了一篇研究成果,針對近幾年使用Rust語言的開源項目中的安全缺陷進行了全面的調查。這項研究調查了5個使用Rust語言開發的軟件系統,5個被廣泛使用的Rust庫,以及兩個漏洞數據庫。調查總共涉及了850處unsafe代碼使用、70個內存安全缺陷、100個線程安全缺陷。
在調查中,研究員不光查看了所有漏洞數據庫中報告的缺陷和軟件公開報告的缺陷,還查看了所有開源軟件代碼倉庫中的提交記錄。通過人工的分析,他們界定出提交所修復的BUG類型,並將其歸類到相應的內存安全/線程安全問題中。所有被調查過的問題都被整理到了公開的Git倉庫中:https://github.com/system-pclub/rust-study[9]
調查結果說明:
Rust語言的safe代碼對於空間和時間內存安全問題的檢查非常有效,所有穩定版本中出現的內存安全問題都和unsafe代碼有關。
雖然內存安全問題都和unsafe代碼有關,但大量的問題同時也和safe代碼有關。有些問題甚至源於safe代碼的編碼錯誤,而不是unsafe代碼。
線程安全問題,無論阻塞還是非阻塞,都可以在safe代碼中發生,即使代碼完全符合Rust語言的規則。
大量問題的產生是由於編碼人員沒有正確理解Rust語言的生命週期規則導致的。
有必要針對Rust語言中的典型問題,建立新的缺陷檢測工具。
那麼這份調查報告背後Rust 的安全性是如何保證的呢? Unsafe Rust 又是為什麼Unsafe 呢?
所有權:Rust 語言內存安全機制
Rust 的設計深深地吸取了關於安全系統編程的學術研究的精髓。特別是,與其他主流語言相比,Rust 設計的最大特色在於採用了所有權類型系統(在學術文獻中通常稱為仿射或子結構類型系統36[10])。
所有權機制,就是Rust 語言借助類型系統,承載其“內存安全”的思想,表達出來的安全編程語義和模型。
所有權機制要解決的內存不安全問題包括:
引用空指針。
使用未初始化內存。
釋放後使用,也就是使用懸垂指針。
緩衝區溢出,比如數組越界。
非法釋放已經釋放過的指針或未分配的指針,也就是重複釋放。
注意,內存洩露不屬於內存安全問題範疇,所以Rust 也不解決內存洩露問題。
為了保證內存安全,Rust 語言建立了嚴格的安全內存管理模型:
所有權系統。每個被分配的內存都有一個獨佔其所有權的指針。只有當該指針被銷毀時,其對應的內存才能隨之被釋放。
借用和生命週期。每個變量都有其生命週期,一旦超出生命週期,變量就會被自動釋放。如果是藉用,則可以通過標記生命週期參數供編譯器檢查的方式,防止出現懸垂指針,也就是釋放後使用的情況。
其中所有權系統還包括了從現代C++ 那裡借鑒的RAII 機制,這是Rust 無GC 但是可以安全管理內存的基石。
建立了安全內存管理模型之後,再用類型系統表達出來即可。 Rust 從Haskell 的類型系統那裡借鑒了以下特性:
沒有空指針
默認不可變
代數數據類型
模式匹配
泛型
模式匹配
泛型
trait 和關聯類型
本地類型推導
為了實現內存安全,Rust 還具備以下獨有的特性:
仿射類型(Affine Type),該類型用來表達Rust 所有權中的Move 語義。
借用、生命週期。
借助類型系統的強大,Rust 編譯器可以在編譯期對類型進行檢查,看其是否滿足安全內存模型,在編譯期就能發現內存不安全問題,有效地阻止未定義行為的發生。
內存安全的Bug 和並發安全的Bug 產生的內在原因是相同的,都是因為內存的不正當訪問而造成的。同樣,利用裝載了所有權的強大類型系統,Rust 還解決了並發安全的問題。 Rust 編譯器會通過靜態檢查分析,在編譯期就檢查出多線程並發代碼中所有的數據競爭問題。
Unsafe Rust :劃分安全邊界
為了和現有的生態系統良好地集成,Rust 支持非常方便且零成本的FFI 機制,兼容C-ABI,並且從語言架構層面上將Rust 語言分成Safe Rust 和Unsafe Rust 兩部分。
其中Unsafe Rust 專門和外部系統打交道,比如操作系統內核。之所以這樣劃分,是因為Rust 編譯器的檢查和跟踪是有能力範圍的,它不可能檢查到外部其他語言接口的安全狀態,所以只能靠開發者自己來保證安全。
Rust 的最終目標並不是完全消除那些危險點,因為在某種程度上,我們需要能夠訪問內存和其他資源。實際上,Rust 的目標是將所有的unsafe元素抽像出來。在考慮安全性時,你需要考慮“攻擊面”,或者我們可以與程序的哪些部分進行交互。像解析器這樣的東西是一個很大的攻擊面,因為:
它們通常可以被攻擊者訪問;
攻擊者提供的數據可以直接影響解析通常需要的複雜邏輯。
你可以進一步分解,將傳統的攻擊面分解成“攻擊面”(可以直接影響程序代碼的部分)和“安全層”,這部分代碼是攻擊面依賴的代碼,但是無法訪問,而且可能存在潛在的Bug。在C 語言中,它們是一樣的:C 語言中的數組根本不是抽象的,所以如果你讀取了可變數量的項,就需要確保所有的不變量都保持不變,因為這是在不安全層中操作,那裡可能會發生錯誤。
所以,Rust 提供了unsafe 關鍵字和unsafe塊,顯式地將安全代碼和訪問外部接口的不安全代碼進行了區分,也為開發者調試錯誤提供了方便。 Safe Rust 表示開發者將信任編譯器能夠在編譯時保證安全,而Unsafe Rust 表示讓編譯器信任開發者有能力保證安全。
有人的地方就有Bug。 Rust 語言通過精緻的設計,將機器可以檢查控制的部分都交給編譯器來執行,而將機器無法控制的部分交給開發者自己來執行。
Safe Rust 保證的是編譯器在編譯時最大化地保障內存安全,阻止未定義行為的發生。
Unsafe Rust 用來提醒開發者,此時開發的代碼有可能引起未定義行為,請謹慎!人和編譯器共享同一個“安全模型”,相互信任,彼此和諧,以此來最大化地消除人產生Bug 的可能。
它也給了開發者一個Unsafe的邊界,這其實也是一種安全邊界。它把你代碼裡的雷區,顯式地標記了出來。團隊代碼裡review的話,可以更快地發現問題。這本身就是一種安全。而反觀C++,你寫出的每一行代碼都是Unsafe的,因為它沒有像Rust這樣明顯的界限(Unsafe 塊)。
能用Safe Rust就用Safe Rust;
以下是我總結的五條使用Unsafe的簡單規範,方便大家做權衡:
能用Safe Rust就用Safe Rust;
為了性能可以使用Unsafe Rust;
在使用Unsafe Rust的時候確保不要產生UB,並且盡量判斷其安全邊界,抽象為Safe 方法;
不過,Rust 社區生態中有一個Rust 安全工作組,該組提供cargo-audit等一系列工具[11],並且維護`RustSecurity` 安全數據庫庫[12]中記錄的Rust生態社區中發現的安全問題。可以方便地檢查Rust 項目中依賴庫的安全問題。
mdnice編輯器
mdnice編輯器
mdnice編輯器
生產力
編程語言生產力,大概可以通過以下三個方面來評估:
學習曲線。
語言工程能力。
領域生態。
學習曲線
學習曲線的高低,依個人水平不同而不同。以下羅列了不同基礎學習Rust 應該注意的地方。
完全零基礎的開發者:掌握計算機基礎體系知識結構,理解Rust語言和硬件/OS層的抽象,理解Rust語言核心概念、以及它的抽像模式,選擇Rust語言的某個適用領域進行實操訓練,通過實踐來提升Rust語言的熟練度和理解深度,同時掌握領域知識。
有C語言基礎:由於C語言開發者對高級語言的抽像不是很理解,所以著重了解掌握Rust所有權機制,包括所有權的語義,生命週期和借用檢查。了解Rust語言的抽像模式,主要是類型和trait;以及Rust本身的的OOP和函數式語言特性。
有C++基礎:C++開發者對於Rust語言的所有權有很好的理解能力,主要精力放在Rust的抽像模式和函數式語言特性上。
有Java/Python/Ruby基礎:著重理解攻克Rust所有權機制、抽像模式、函數式編程語言特性。
有Go基礎:Go語言開發者比較容易理解Rust的類型和trait抽像模式,但Go也是GC語言,所以所有權機制和函數式語言特性是他們的學習重點。
有Haskell基礎:Haskell系的開發者對Rust語言函數式特性能很好的理解,主要攻克所有權機制和OOP語言特性。
所以,對於有一定基礎的開發者來說,學習Rust語言要掌握的幾個關鍵概念有:
1、Rust所有權機制,包括所有權的語義,生命週期和借用檢查
所有權機制是Rust語言最核心的特性,它保證了在沒有垃圾回收機制下的內存安全,所以對於習慣了GC的開發者,理解Rust的所有權是最關鍵的一環,切記這三點:
Rust中的每一個值都有一個被稱為其所有者(owner)的變量。
值有且只有一個所有者。
當所有者(變量)離開作用域,這個值將被丟棄。這其中又涉及到生命週期和借用檢查等概念,是相對比較難啃的一塊硬骨頭。
2、Rust語言的抽像模式,主要是類型和trait。 trait借鑒了Haskell中的Typeclass,它是對類型行為的抽象,可以通俗地類比為其他編程語言裡的接口,它告訴編譯器一個類型必須提供哪些功能語言特性。使用時要遵循一致性,不能定義相互衝突的實現。
3、OOP語言特性。熟悉面向對象編程(OOP)的常見的四個特性:對象、封裝、繼承和多態,可以更好地理解Rust的一些特性,比如impl、pub、trait等等。
4、函數式語言特性。 Rust語言的設計深受函數式編程的影響,看到函數式特性,數學不好的人可能會望而卻步,因為函數式編程語言的最大特點是把運算過程盡量寫成一系列嵌套的函數調用,在Rust中,掌握閉包和迭代器是編寫函數式語言風格的高性能Rust代碼的重要一環。
語言工程能力
Rust 已經為開發工業級產品做足了準備。
為了保證安全性,Rust 引入了強大的類型系統和所有權系統,不僅保證內存安全,還保證了並發安全,同時還不會犧牲性能。
為了保證支持硬實時系統,Rust 從C++那裡借鑒了確定性析構、RAII 和智能指針,用於自動化地、確定性地管理內存,從而避免了GC 的引入,因而就不會有“世界暫停”的問題了。這幾項雖然借鑒自C++,但是使用起來比C++更加簡潔。
為了保證程序的健壯性,Rust 重新審視了錯誤處理機制。日常開發中一般有三類非正常情況:失敗、錯誤和異常。但是像C 語言這種面向過程的語言,開發者只能通過返回值、goto 等語句進行錯誤處理,並且沒有統一的錯誤處理機制。而C++和Java 這種高級語言雖然引入了異常處理機制,但沒有專門提供能夠有效區分正常邏輯和錯誤邏輯的語法,而只是統一全局進行處理,導致開發者只能將所有的非正常情況都當作異常去處理,這樣不利於健壯系統的開發。並且異常處理還會帶來比較大的性能開銷。
Rust 語言針對這三類非正常情況分別提供了專門的處理方式,讓開發者可以分情況去選擇。
對於失敗的情況,可以使用斷言工具。
對於錯誤,Rust 提供了基於返回值的分層錯誤處理方式,比如Option 可以用來處理可能存在空值的情況,而Result 就專門用來處理可以被合理解決並需要傳播的錯誤。
對於異常,Rust 將其看作無法被合理解決的問題,提供了線程恐慌機制,在發生異常的時候,線程可以安全地退出。
通過這樣精緻的設計,開發者就可以從更細的粒度上對非正常情況進行合理處理,最終編寫出更加健壯的系統。
為了提供靈活的架構能力,Rust 使用特質(trait) 來作為零成本抽象的基礎。特質面向組合而非繼承,讓開發者可以靈活地架構緊耦合和松耦合的系統。 Rust 也提供了泛型來表達類型抽象,結合trait 特性,讓Rust 擁有靜態多態和代碼復用的能力。泛型和trait 讓你可以靈活使用各種設計模式來對系統架構進行重塑。
為了提供強大的語言擴展能力和開發效率,Rust 引入了基於宏的元編程機制。 Rust提供了兩種宏,分別是聲明宏和過程宏。聲明宏的形式和C的宏替換類似,區別在於Rust會對宏展開後的代碼進行檢查,在安全方面更有優勢。過程宏則讓Rust 在代碼復用、代碼生成擁有強大的能力。
為了和現有的生態系統良好地集成,Rust 支持非常方便且零成本的FFI 機制,兼容C-ABI,並且從語言架構層面上將Rust 語言分成Safe Rust 和Unsafe Rust 兩部分。其中Unsafe Rust 專門和外部系統打交道,比如操作系統內核。之所以這樣劃分,是因為Rust 編譯器的檢查和跟踪是有能力範圍的,它不可能檢查到外部其他語言接口的安全狀態,所以只能靠開發者自己來保證安全。 Unsafe Rust 提供了unsafe 關鍵字和unsafe 塊,顯式地將安全代碼和訪問外部接口的不安全代碼進行了區分,也為開發者調試錯誤提供了方便。 Safe Rust 表示開發者將信任編譯器能夠在編譯時保證安全,而Unsafe Rust 表示讓編譯器信任開發者有能力保證安全。
有人的地方就有Bug。 Rust 語言通過精緻的設計,將機器可以檢查控制的部分都交給編譯器來執行,而將機器無法控制的部分交給開發者自己來執行。 Safe Rust 保證的是編譯器在編譯時最大化地保障內存安全,阻止未定義行為的發生。 Unsafe Rust 用來提醒開發者,此時開發的代碼有可能引起未定義行為,請謹慎!人和編譯器共享同一個“安全模型”,相互信任,彼此和諧,以此來最大化地消除人產生Bug 的可能。
為了讓開發者更方便地相互協作,Rust 提供了非常好用的包管理器Cargo[13]。 Rust 代碼是以包(crate)為編譯和分發單位的,Cargo 提供了很多命令,方便開發者創建、構建、分發、管理自己的包。 Cargo 也提供插件機制,方便開發者編寫自定義的插件,來滿足更多的需求。比如官方提供的rustfmt 和clippy 工具,分別可以用於自動格式化代碼和發現代碼中的“壞味道”。再比如,rustfix 工具甚至可以幫助開發者根據編譯器的建議自動修復出錯的代碼。 Cargo 還天生擁抱開源社區和Git,支持將寫好的包一鍵發佈到crates.io 網站,供其他人使用。
為了方便開發者學習Rust,Rust 官方團隊做出瞭如下努力:
獨立出專門的社區工作組,編寫官方Rust Book,以及其他各種不同深度的文檔,比如編譯器文檔、nomicon book 等。甚至組織免費的社區教學活動Rust Bridge,大力鼓勵社區博客寫作,等等。
Rust 語言的文檔支持Markdown 格式,因此Rust 標準庫文檔表現力豐富。生態系統內很多第三方包的文檔的表現力也同樣得以提升。
提供了非常好用的在線Playground 工具,供開發者學習、使用和分享代碼。
Rust 語言很早就實現了自舉,方便學習者通過閱讀源碼了解其內部機制,甚至參與貢獻。
Rust 核心團隊一直在不斷改進Rust,致力於提升Rust 的友好度,極力降低初學者的心智負擔,減緩學習曲線。比如引入NLL 特性來改進借用檢查系統,使得開發者可以編寫更加符合直覺的代碼。
為了方便Rust 開發者提升開發效率,Rust 社區還提供了強大的IDE 支持。 VSCode/Vim/Emacs + Rust Analyzer 成為了Rust 開發的標配。當然JetBrains家族的IDEA/ Clion 也對Rust 支持十分強力。
mdnice編輯器
mdnice編輯器
mdnice編輯器
Rust 與開源
Rust 語言自身作為一個開源項目,也是現代開源軟件中的一顆璀璨的明珠。
在Rust 之前誕生的所有語言,都僅僅用於商用開發,但是Rust 語言改變了這一狀況。對於Rust 語言來說,Rust 開源社區也是語言的一部分。同時,Rust 語言也是屬於社區的。
在Rust 基金會成立以後,Rust 團隊也在不斷探索新的開源治理方案。
mdnice編輯器
mdnice編輯器
mdnice編輯器
Rust 語言的不足
Rust 雖然有很多優勢,但肯定也存在一些缺點。
缺乏針對Rust 語言特有內存不安全問題的各種檢測工具。
mdnice編輯器
mdnice編輯器
mdnice編輯器
Rust 生態基礎庫和工具鏈
Rust 生態日趨豐富,很多基礎庫和框架都會以包(crate) 的方式發佈到crates.io[14] ,截止目前,crates.io 上面已經有62981 個crate,總下載量已經達到7,654,973,261次。
按包的使用場景分類,Crates.io 最流行的幾個場景依次如下:
命令行工具(3133 crates)
no-std 庫(2778 crates)
開發工具(測試/ debug/linting/性能檢測等, 2652 crates)
Web 編程(1776 crates)
API 綁定(方便Rust 使用的特定api 包裝,比如http api、ffi 相關api等,1738 crates)
網絡編程(1615 crates)
數據結構(1572 crates)
嵌入式開發(1508 crates)
加密技術(1498 crates)
異步開發(1487 crates)
算法(1200 crates)
科學計算(包括物理、生物、化學、地理、機器學習等,1100 crates)
常用知名基礎庫和工具鏈
序列化/反序列化:Serde[15]
其中已經湧現出不少優秀的基礎庫,都可以在crates.io 首頁裡看到。這裡羅列出一些:
序列化/反序列化:Serde[15]
命令行開發:clap [16]/ structopt[17]
異步/Web/網絡開發:tokio [18] / tracing [19] /async-trait [20] / tower [21]/ async-std [22] tonic [23]/ actix-web [24]/smol [25 ]/ surf [26]/ async-graphql [27]/ warp /[28] tungstenite [29]/ encoding_rs [30]/ loom [31]/ Rocket[32]
FFi 開發:libc [33]/ winapi [34]/ bindgen [35]/ pyo3 [36]/ num_enum [37]/ jni [38]/ rustler_sys[39]/ cxx [40]/ cbindgen [41]/ autocxx- bindgen [42]
WebAssembly: wasm-bindgen[67]/ wasmer [68]/ wasmtime [69]/ yew [70]
API 開發: jsonwebtoken [43]/ validator [44]/ tarpc [45]/ nats [46]/ tonic[47]/ protobuf [48]/ hyper [49]/ httparse [50]/ reqwest [51] / url [ 52]
解析器:nom [53]/ pest [54]/ csv [55]/ combine [56]/ wasmparser [57]/ ron [58]/ lalrpop [59]
密碼學:openssl [60]/ ring [61]/ hmac [62]/ rustls[63] / orion[64] / themis[65] / RustCrypto[66]
數據庫開發:diesel [71]/ sqlx [72]/ rocksdb [73]/ mysql [74]/ elasticsearch [75]/ rbatis [76]
並發:crossbeam [77]/ parking_lot [78]/ crossbeam-channel [79]/ rayon [80]/ concurrent-queue[81]/ threadpool [82] / flume [83]
嵌入式開發:embedded-hal [84]/ cortex-m [85]/ bitvec [86]/ cortex-m-rtic [87]/ embedded-dma [88]/ cross [89]/ Knurling Tools[90]"rapier") / Rustcraft[115] Nestadia[116]/ naga[117]/ Bevy Retro[118]/ Texture Generator[119] / building_blocks[120] / rpg-cli [121]/ macroquad[122]
TUI/GUI 開發:winit [123]/ gtk [124]/ egui [125]/ imgui [126]/ yew [127]/ cursive [128]/ iced [129]/ fontdue [130]/ tauri [131]/ druid [132]
mdnice編輯器
mdnice編輯器
mdnice編輯器
下面來盤點不同領域內國內外的Rust 項目。通過提供代碼量、團隊規模、項目週期相關數據,希望可以讓大家對Rust 領域應用和開發效率能有一個比較直觀的認識。
mdnice編輯器
mdnice編輯器
mdnice編輯器
關鍵字:數據庫/ 分佈式系統/ CNCF
介紹
介紹
介紹
介紹
TiKV [133]是一個開源的分佈式事務Key-Value 數據庫,專注為下一代數據庫提供可靠、高質量、實用的存儲架構。最初由PingCAP 團隊在目前,TiKV 已經在知乎、一點資訊、Shopee、美團、京東雲、轉轉等多行業頭部企業得到上線應用。
TiKV 通過Raft 一致性算法來實現數據多副本之間的一致性,本地採用了RocksDB 存儲引擎存儲數據,同時TiKV 支持數據自動切分和遷移。 TiKV 的跨行事務最初參考Google Percolator 事務模型,並進行了一些優化,提供快照隔離與帶鎖快照隔離,支持分佈式事務。
2018 年8 月被CNCF 宣布接納為沙箱雲原生項目,在2019 年5 月從沙箱晉級至孵化項目。
代碼與團隊規模
TiKV 項目包含Rust 代碼行數大約30 萬行(包含測試代碼)。
TiKV 是全球性開源項目,可以從貢獻者名單[134]來查看團隊規模。 TiKV 組織中也包含了一些Go/Cpp 項目,這個並不算在內,只統計參與Rust 項目的人力規模。
主力開發:20人左右。
社區貢獻:300 多人。
項目週期
TiKV 是作為TiDB 的底層存儲跟隨TiDB 演進。 TiDB 為Go 開發,TiKV 為Rust 開發。
2016 年1 月作為TiDB 的底層存儲引擎設計並開發。
2016 年4 月開源發布第一版。
2017 年10 月16 日,TiDB 發布GA 版(TiDB 1.0), TiKV 發布1.0 。
2018 年4 月27 日,TiDB 發布2.0 GA 版, TiKV 發布2.0 。
2019 年6 月28 日,TiDB 發布3.0 GA 版本, TiKV 發布3.0 。
2020 年5 月28 日,TiDB 發布4.0 GA 版本, TiKV 發布4.0。
2021 年4 月07 日,TiDB 發布5.0 GA 版本, TiKV 發布5.0。
小評
有些朋友可能比較關注Rust 開發效率如何,並且想對其量化,尤其是想對比C/ Cpp / Go 等其他語言的開發效率。
關鍵字:實時數據倉庫/ 創業/ 天使輪
介紹
介紹
介紹
介紹
TensorBase[135] 是金明劍博士於2020 年8月啟動的創業項目,從一個現代的全新視角出發,用開源的文化和方式,重新構建一個Rust下的實時數據倉庫,服務於這個海量數據時代的數據存儲和分析。 TensorBase 項目目前已獲得世界知名創業投資加速機構的天使輪投資。
代碼與團隊規模
因為TensorBase 是構建於Apache Arrow[136] 和Arrow DataFusion[137] 之上,所以代碼統計排除掉這兩個項目的依賴。
TensorBase 核心代碼行數54000 多行。
團隊規模:
主力開發:1人。
社區貢獻:13 人。
因為是新項目,開源社區還在建設中。
項目週期
關鍵字:Dataflow/ 分佈式系統/創業
介紹
介紹
介紹
介紹
Timely Dataflow[138] 是基於微軟這篇Timely Dataflow 論文:《Naiad: A Timely Dataflow System》[139]的現代化Rust 實現。是clockworks.io[140] 公司的開源產品。
在分佈式系統中對流式數據進行複雜的處理,比如多次迭代或者遞增計算是非常困難的。 Storm, Streaming Spark, MillWheel 都不能很好的適應各種應用複雜的需求。 Naiad 通過引入timestamp 的概念,給出了一個非常low-level 的模型,可以用來描述任意複雜的流式計算。
dataflow系統包羅萬象,MapReduce,Spark都可以算是其中代表。 Timely dataflow 給出了一個完全基於時間的抽象,統一了流式計算和迭代計算。當你需要對流式數據並行處理且需要迭代控制的時候,可以使用Timely Dataflow 。
代碼與團隊規模
Rust 代碼量大約13000 行。
團隊規模:
主力開發:4人。
社區貢獻:30多人。
項目週期
2017年9月7號,0.3.0 版本。
2018年6月28號,0.6.0 版本。
2018年9月16號,0.7.0 版本。
2018年12月3號,0.8.0 版本。
2019年3月31號,0.9.0 版本。
2019年7月10號,0.10.0 版本。
關鍵字:數據庫/ 學術論文項目
介紹
介紹
介紹
介紹
Noria [142] 是一種新的流式數據流系統,旨在作為基於MIT Jon Gjengset[143] 的博士學位論文[144]的重型Web應用程序的快速存儲後端,也參考了OSDI'18的論文[145]。它類似於數據庫,但支持預計算和緩存關係查詢結果,以便加速查詢。 Noria 自動將緩存的結果保持為底層數據,存儲在持久性基礎表中。 Noria使用部分狀態數據流來減少內存開銷,並支持動態,運行時數據流和查詢更改。
代碼與團隊規模
Rust 代碼行數大約59000 多行。
團隊規模:
主力貢獻者:2人
社區貢獻者:21人
項目週期
因為是個人學術研究項目,所以發布週期沒有那麼明顯。
項目週期2016年7月30 ~ 2020年4月30,一共5000多commit。
Vector (國外/開源/數據管道)
關鍵字:數據管道/分佈式系統/創業
Vector[146] 是Timer 公司構建的一款高性能的、端到端的(代理和聚合器)可觀察性數據管道。它是開源的,比該領域(Logstash、Fluentd之類)的所有替代方案快10倍。目前像豆瓣、checkbox.ai、fundamentei、BlockFi、Fly.io 等公司使用了Vector 。點擊此處[147]查看官方性能報告,點擊此處[148]查看目前生產環境中使用Vector 的公司。
代碼與團隊規模
代碼量大約18 萬行Rust 代碼。
團隊規模:
主力開發:9人
社區貢獻:140 人
項目週期
2019年3月22,初始版本發布。
2019年6月10,0.2.0版本發布
2019年7月2,0.3.0版本發布
2019年9月25,0.4.0版本發布
2019年10月11,0.5.0版本發布
2019年12月13,0.6.0版本發布
2020年1月12,0.7.0版本發布
2020年2月26,0.8.0版本發布
2020年4月21,0.9.0版本發布
2020年7月23,0.10.0版本發布
2021年3月12,0.11.0 ~ 0.12 版本發布
2021年4月22,0.13.0版本發布
2021年6月3,0.14.0版本發布
Arrow-rs (國外/開源/大數據標準)
關鍵字:大數據/數據格式標準/Apach
arrow-rs[149] 是Apache Arrow 的Rust 實現。 Apache Arrow 是一種適合異構大數據系統的內存列存數據格式標準。它有一個非常大的願景:提供內存數據分析(in-memory analytics) 的開發平台,讓數據在異構大數據系統間移動、處理地更快。
Arrow 從2.0 版本開始引入Rust[150] ,從4.0 開始Rust 實現遷移到了獨立倉庫arrow-rs 。
Arrow的Rust實現實際上由幾個不同的項目組成,包括以下幾個獨立crate 和庫:
arrow[151],arrow-rs 核心庫,包含在arrow-rs 中。
arrow-flight [152],arrow-rs 組件之一,包含在arrow-rs 中。
parquet[153],arrow-rs 組件之一,包含在arrow-rs 中。在大數據生態內,Parquet 是最為流行的文件存儲格式。
DataFusion[154],一個可擴展的內存查詢執行引擎,使用Arrow作為其格式。
Ballista[155],一個分佈式計算平台,由Apache Arrow和DataFusion驅動,包含在DataFusion 中。
代碼與團隊規模
arrow-rs 各相關組件加起來,Rust 代碼量大約18 萬行。
團隊規模:
主力開發:大約10 人
社區貢獻:550 多人
項目週期
項目DataFusion 在2016 年就開始構建了,後來進入了Apache Arrow 項目。
以arrow-rs 4.0 開始算:
2021 年4 月18 ,版本4.0 發布。
2021 年5 月18,版本4.1 發布。
2021 年5 月30, 版本4.2 發布。
2021 年6 月11, 版本4.3 發布。
InfluxDB IOx (國外/ 開源/時序數據庫)
關鍵字:時序數據庫/分佈式
InfluxDB IOx[156],是InfluxDB 的下一代時序引擎,使用Rust + Aarow 來重寫。
現有設計主要有以下幾個致命性問題:
無法解決時間線膨脹的問題
在雲原生環境下,對內存管理要求比較嚴格,這意味mmap 不在適用,而且InfluxDB 需要支持無本地盤的運行模式
由於索引與數據分開存儲,導致高效的數據導入導出功能難以實現
上述這三個問題都是現有設計的核心之處,因此要想支持現階段需求,重寫是個比較好的選擇。
代碼與團隊規模
InfluxDB IOx 代碼量大約是16萬行Rust 代碼。
團隊規模:
主力開發:5人
社區貢獻:24 人
項目週期
關鍵字:時序數據庫
介紹
介紹
介紹
介紹
CeresDB 是螞蟻集團研發的一款TP/AP 融合時序數據庫,滿足金融時序、監控、IOT 等場景下的海量時序數據的存儲、多維查詢下鑽和實時分析需求。有開源計劃,但目前暫未開源。
團隊規模
目前數據庫開發大約8-10個人。
其他信息還未可知。
tantivy (國外/開源/全文檢索)
關鍵字:全文檢索/ lucene
tantivy[157] 是一個由Apache Lucene 啟發的全文搜索引擎庫,用Rust 實現。
tantivy 性能卓越,這裡有一個基於Rust + Tantivy + AWS 構建的應用:提供十億網頁搜索並生成常見單詞雲[158]。
代碼及團隊規模
代碼量大約為50000 行Rust 代碼。
團隊規模:
主力開發:1 人
社區貢獻:85人
關鍵字:知乎/ lucene
介紹
介紹
介紹
介紹
Rucene[159] 是知乎團隊開源的一款基於Rust 實現的搜索引擎。 Rucene不是完整的應用程序,而是可以輕鬆用於將完整文本搜索功能添加到應用程序的代碼庫和API。它是對Apache Lucene 6.2.1 項目的Rust 移植。
代碼及團隊規模
代碼量大約為10 萬行Rust 代碼。
團隊規模:
可能因為是公司內部項目開源化,目前沒有迭代出具體語義版本。在知乎內是用於生產環境的。
mdnice編輯器
mdnice編輯器
mdnice編輯器
雲原生
雲原生領域包括:機密計算、Serverless、分佈式計算平台、容器、WebAssembly、運維工具等
StratoVirt (國內/開源/容器)
關鍵字:容器/ 虛擬化/ Serverless
StratoVirt[160] 是華為OpenEuler 團隊研發的基於Rust的下一代虛擬化平台。
Strato,取自stratosphere,意指地球大氣層中的平流層,大氣層可以保護地球不受外界環境侵害,而平流層則是大氣層中最穩定的一層;類似的,虛擬化技術是操作系統平台之上的隔離層,既能保護操作系統平台不受上層惡意應用的破壞,又能為正常應用提供穩定可靠的運行環境;以Strato入名,寓意為保護openEuler平台上業務平穩運行的輕薄保護層。同時,Strato也承載了項目的願景與未來:輕量、靈活、 安全和完整的保護能力。
StratoVirt是計算產業中面向雲數據中心的企業級虛擬化平台,實現了一套架構統一支持虛擬機、容器、Serverless三種場景,在輕量低噪、軟硬協同、安全等方面具備關鍵技術競爭優勢。 StratoVirt在架構設計和接口上預留了組件化拼裝的能力和接口,StratoVirt可以按需靈活組裝高級特性直至演化到支持標準虛擬化,在特性需求、應用場景和輕快靈巧之間找到最佳的平衡點。
代碼與團隊規模
代碼量大約是27000 行Rust 代碼。
團隊規模:
主力開發:4 人。
社區貢獻:15人。
項目週期
2020-09-23,發布0.1.0 版本。
2021-03-25,發布0.2.0 版本。


