理清頭腦混沌,覺醒心智天地python
在 2020 年 3 月 28 號,我爲
上海科技大學GeekPie社團 WorkShop#7「關於Rust你須要瞭解的…」 共享了一次分享,本文是該分享的文字版。內容比視頻裏的多了一些細節描述,但儘量地精煉。
視頻版見B站回放:https://www.bilibili.com/video/BV1ti4y1b7xy/
https://c-t.work/s/74b9dcf657be4d程序員
https://cowtransfer.com/s/c98f417a076d48 密碼shanghaitech算法
https://c-t.work/s/37a60fd0da9041 密碼shanghaitech 數據庫
另附加兩篇關於「K-Rust :Rust 可執行形式語義」的論文:編程
上科大宋老師: https://arxiv.org/abs/1804.10806 設計模式
Cyber Security Lab - NTU : https://arxiv.org/abs/1804.07608數組
今天給你們帶來的主題是:編程語言中的變革者 | 勇於打造理想世界的 Rust 。
Rust 語言的誕生。任何一門技術的存在,它都是爲了解決一個問題。那麼 Rust 是爲了解決什麼問題而存在?這是咱們面對Rust語言的時候,要必需要先搞清楚的一個問題。緩存
Rust 語言有什麼特性。經過了解Rust語言的特性,來看看 Rust 語言如何解決它想解決的那些問題。
瞭解 Rust 在生產環境中有哪些應用。
-
對於當今大學生來講,學習 Rust 的意義何在?Rust 是如何讓你成爲一個更好的開發者?
Rust 語言到底想解決什麼問題呢?這就要回顧一下計算機和編程語言發展歷史了。
咱們如今思考一個問題,假如沒有 Rust 語言,時代的進程是什麼樣的?
在歷史的早期,程序員們寫代碼,都是直接機器碼編程,就是紙帶機,你們都在那戳一個個小孔來編程。
隨着計算機發展,直接打孔編程的方式已經知足不了需求了,效率太慢,因而有人發明了彙編語言。
彙編語言是對機器碼的一一對應,能夠直接翻譯爲機器碼,彙編語言第一次提高了代碼的可讀性。這個重要性,不亞於生物進化史上,猿猴學會了直立行走。
隨着時代的發展,又有人發明了 C 語言來替代彙編語言。但其實,真正的歷史不是這麼一蹴而就的,從彙編到C語言,是伴隨着Unix系統的發展,經歷了彙編、A語言、B語言才最終達成了C語言。C語言擁有強大的功能,高性能,且不依賴於具體機器系統的可移植性,幫助Unix順利發展。
Unix 和C 語言就這麼相輔相成的,發展至今,統治了世界。你們確定都用過從Unix系統演化而來的Linux、BSD、MacOS等等。
後來,Bjarne博士在對Unix內核作分析的時候,發現沒有合適的工具能有效分析內核分佈而形成網絡流量,以及怎樣將內核模塊化,因而他就考慮,是否應該發明一種新的語言。在當時若是想和C競爭,就必須錯開和C的應用領域,而且同時擁有和C同樣的性能。最後他基於C語言,而且從Simula汲取了類的概念,從Ada語言中取來了模板、名字空間和異常等,最終發明了Cpp語言,也就是 C with Class。
這在當時,Cpp的設計確實是很是先進的,而且在1995年以前在工業界很是流行。
可是當Java和C#出現以後,以及硬件價格開始大規模降低的時候,Cpp受到了必定的衝擊。
其實在Java誕生前夜,Sun公司的人還在考慮使用C++來發明一種語言,可是他們發現,C++存在很大的問題,C++太複雜,以致於不少開發者在錯誤使用它。而且C++缺少可移植安全性、分佈式程序設計和多線程功能、垃圾回收等。由於他們想要一種易於移植到各類設備平臺的語言。
一直到1994年,Sun公司的人改變了努力的目標,他們認爲,隨着網景公司Mosaic瀏覽器的到來,互聯網將變得愈來愈流行,而這一遠景是他們在有線電視網中看到的。因而Java誕生了。Java隨着瀏覽器的發展而發展,可是後來受到微軟的阻攔以後,IE裏雖然沒有了Java平臺,可是Java此時已經普及到了服務端和手持設備上。JSP和其餘Java技術也就流行了起來。
隨着互聯網的高速發展,一直到2004年,Ruby on Rails的出世,帶動了硅谷互聯網創業浪潮,這算是互聯網的加速時代。
一直到2015年,互聯網創業浪潮才退去。互聯網流量到了史無前例的高度。爲了提供更穩定和更好的服務,互聯網逐漸進入了雲原生時代。Docker的橫空出世,讓Go語言也開始普及了。
咱們回顧了編程語言的發展歷史,看的出來,基本上一些主流語言的誕生,都是隨着時代的變化而出現。
好像沒有Rust語言,這個時代也會繼續發展下去,毫無影響。但其實咱們要再仔細的推敲一下,就不可貴出一些結論:
其實當時還有更加安全的語言Ada,其實Ada的設計理念很是先進,包括了「編譯期類型檢查、肯定性內存管理、內置安全併發模型,無數據競爭、泛型」等特性。可是爲何沒有成爲主流呢?這是當時那個性能爲王的時代的必然結果。Unix的發展,性能是第一,可是安全並非主要的,由於我的電腦並無普及開。
因此,追求性能,犧牲了安全性,因而造就了一個不安全的世界。
第2、Java爲了突破C/Cpp的桎梏,進一步引入了GC
Java誕生之時,正是互聯網發展初期,我的電腦獲得了必定程度的普及,安全性開始被注重了起來。可是Java是經過引入GC來解決內存安全的問題,把內存管理交給程序和算法,這個思路是對的,可是勢必會犧牲一部分性能。
第3、摩爾定律致使硬件降低,互聯網創業潮,各類動態語言流行,開發效率倍增
高速的發展,勢必會積累不少技術債務,軟件的工程性遭到了挑戰。編程語言上手門檻變得愈來愈低,這個時期,只注重生產力,碼農和碼畜由此誕生。
創業浪潮雖然退去,可是流量激增,操做系統的瓶頸凸顯。首先,須要最大化利用資源,容器比虛擬機更節省資源。其次,容器的沙盒化更容易進程間相互隔離,安全性增長。
時間到了2020年了,接下來我也想讓你們和我一塊兒思考一下,互聯網下一步的發展方向該看重什麼?
數字金融,數字貨幣、區塊鏈正在逐漸走進咱們的生活。
世界的不肯定性,更加促進了互聯網的發展,一場疫情,老師都變成主播了,以前咱們程序員最期待的遠程辦公,也忽然實現了。
你會發現,各類軟件和數字產品,正在侵入咱們的平常生活。
然而,咱們的互聯網大廈倒是漏洞百出。黑產盛行,網站被破解,隱私被賣,網絡詐騙橫行,數字貨幣被盜,智能門鎖被破解,汽車被控制。安全,成爲了當下,以及將來發展的重中之重。
咱們的互聯網世界,急需改變。表面上看似平靜,實則暗流涌動。
有人的地方就有Bug,由於咱們的大腦沒法作到機器那樣的精確思考,總會出現盲區。
非也,在2019年2月,微軟安全響應中心的工程師在以色列舉行的Bluehat會議上,發表過一篇演講:《微軟:70%的安全漏洞都是內存安全問題》。
咱們不是要解決全部的Bug,也許將來人工智能有高度發展之後可能會作到?但如今咱們能作的,就是儘量地去消滅能夠被消滅的問題。好比這個內存安全問題。
安全的世界,是須要付出代價創造出來的,而不是它本身來的。
至於咱們要付出什麼代價,先按下不表,咱們接着日後面講。
咱們人類之因此能發展到如今,是由於老是存在一些有遠見的非凡人物,爲咱們看清和指明方向。
在2006年,Mozilla僱員 Graydon Hore,意識到了咱們剛纔講的那個問題:將來的互聯網,必定會同時注重安全和性能。性能確定是不斷的追求。可是安全,纔剛剛開始被注重。
Graydon 做爲一名職業的編程語言,平常工做就是給其餘語言開發編譯器和工具集,長此以往,他其實早已萌生了本身開發一門編程語言的想法。
這門編程語言,必需要承載他對將來互聯網世界的願景。也就是,內存和性能兼備。
而且,他在平常和衆多語言打交道的日子裏,看到了不少非主流語言裏也包含了優秀的特性,他不想讓這些語言的優秀特性被埋沒。
-
-
-
Rust 語言的Logo就是右側像齒輪同樣的圖案。
「Rust」這個名字包含了 GH 對這門語言的預期。在天然界有一種叫做鏽菌(Rust Fungi)的真菌,這種真菌寄生於植物中,引起病害,並且號稱「本世紀最可怕的生態病害」之一。這種真菌的生命力很是頑強,其在生命週期內能夠產生多達 5 種孢子類型,這 5 種生命形態還能夠相互轉化,若是用軟件術語來描述這種特性,那就是「魯棒性超強」。能夠回想一下 Rust 的 Logo 形狀(如圖 1-1 所示),Logo 上面有 5 個圓圈,也和鏽菌這 5 種生命形態相對應,暗示了 Rust 語言的魯棒性也超強。「Rust」也有「鐵鏽」的意思,暗合「裸金屬」之意,表明了 Rust 的系統級編程語言屬性,有直接操做底層硬件的能力。此外,「Rust」在字形組合上也糅合了「Trust」和「Robust」,暗示了「信任」與「魯棒性」。
Rust 名字起的這麼好,語言目標也很是使人激動,那麼, Rust 是如何作到這個目標的呢?
首先,和 Python 爲表明性的動態語言相比,Rust有如下優勢:
-
-
-
-
-
-
-
-
-
-
-
-
不會拋出ConcurrentModification異常
-
-
擁有一致的構建系統和依賴管理,Java的則不少可選項
-
-
-
-
-
-
和 Go 語言相比,優點在於:
無GC 暫停
無空指針
更優雅的錯誤處理
安全併發
更健壯的類型系統
零成本抽象
統一的依賴管理
-
-
-
-
-
-
-
-
有的人可能會認爲:不是編程語言的問題,而是寫代碼的人水平不夠,纔出現這種安全問題。
然而,沒有程序員是全能的,Rust 編譯器捕獲的錯誤,有可能超出程序員的經驗以外。難道開車上路,有更好的司機,就不須要安全帶了嗎?非也。這個世界變得愈來愈複雜,咱們須要像 Rust 這樣帶有安全防禦的語言來防止錯誤,保證程序的正確性。
你們可能會以爲 Rust 語言功能如此強大,它的設計是否是很複雜呢?
Rut其實沒有那麼複雜,首先它遵循有一套本身的設計哲學:
-
-
-
-
因此,Rust 語言的總體架構,必須遵循上述哲學。
可是Rust語言爲了保持簡潔,它採用了基於類型系統的語言架構。
在類型系統之上,包含了一層語義,即全部權語義,以及承載了兩種編程範式,OOP和FP。
在類型系統之下,包含了一個半自動內存管理系統,來自於C++ 11 引入的RAII,基於棧來半自動管理堆。
Rust編譯器會根據類型系統,在編譯期進行各類安全檢查、展開抽象等等。
這種語言架構,致使的結果就是:要求開發者對於Rust語言的心智模型,必須和編譯器達成一致。若是不一致,那麼開發的時候,就會遇到編譯器的阻攔。
開發者,必需要先學會 Rust,而且在寫代碼以前,作好類型設計,才能更高效地利用 Rust 產出正確的代碼。
接下來,咱們看一些代碼來讓你們感覺一下 Rust 的特性。
Rust 中使用 let 來聲明一個綁定(變量),上面的 x 意味着它綁定了一塊內存區域,它對這塊內存區域有全部權。
第二行表明,x 將它的全部權交給了 y,這在 Rust 裏面叫作「全部權轉移」。
第三行,調用 drop 函數想釋放 x 綁定的內存區域,可是 Rust 編譯器不會讓這行代碼經過編譯。
這是由於 ,Vec 是默認存儲在堆內存。假設,若是不發生全部權轉移的話,在棧內存上面,將出現同時指向 Vec 堆內存的兩個指針,就會發生「雙重釋放」同一塊內存區域的安全問題。
在C/Cpp中,處理這類問題,通常都是靠開發者自己的能力來決定,可是在 Rust 裏,無論是什麼水平的開發者,必須先經過編譯器這一關。這就有效保證了程序的正確性。而且這一切都是在編譯期完成的,而非運行時檢查。
Rust 也經過編譯期借用檢查來保證不會出現懸垂指針的問題。
上面代碼中,定義了 x 可變綁定(綁定默認不可變,此處經過 mut 修飾符顯式指定爲可變),它的值是 Vec 動態數組。
第二行,定義了 first 綁定,它的值是指向 x 數組第一位元素的引用。
在最後一行的打印語句中,想要對 first 進行解引用操做,可是這個時候 Rust 編譯器會阻止代碼經過編譯。這是由於,first 是對 x 的引用,可是 x 的全部權已經被轉移了,first 就自動失效。 若是 first 沒有自動失效會發生什麼呢?它可能會變成一個野指針。
Rust 中綁定默認不可變。上圖代碼中前兩行會正常編譯,可是最後一行就不能正常編譯了。
這是由於,push 方法會自動對 v 進行可變借用,此處須要的是 &mut v,可是由於 v 是不可變綁定,因此此處沒法進行可變借用,從而沒法對數組進行 push 修改。
默認不可變,顯式指定可變。能夠幫助開發者在寫代碼的過程當中,就想清楚狀態如何改變,從而在代碼設計層面必定程度上來保證正確性。
這段代碼和上面的代碼同理。顯式的可變,會加強代碼的可讀性,提升代碼的正確性。
Rust 默認線程安全。由於 Rust 的類型系統,能夠經過 Send 和 Sync 這兩個 trait 對類型進行鑑別,哪些類型是能夠在線程間安全傳遞的,哪些不能夠。
上面代碼中的 Rc,就是非原子類型的,因此它沒有被實現 Send 和 Sync,因此它不是線程安全的類型。在上面代碼中,編譯器不會讓第三和第四行代碼經過編譯。
相反,Arc 則原子類型的,它實現了 Send 和 Sync,那麼 Rust 編譯器就能在編譯期識別它是線程安全的類型。
1. std::thread::spawn 是建立了一個子線程,而且在該線程中執行了閉包,該閉包中使用了 v.push 爲數組中插入一個數字。試想一下,若是這行代碼經過編譯,會發生什麼事情?主線程中最後會調用 pop 方法,可是由於線程間其實並不一樣步, pop出來的元素極可能不是 42 。這是線程不安全的代碼。因此, Rust 編譯器會阻止代碼經過編譯。Rust 編譯器實際上是經過全部權機制進行判斷,v 此時被傳入了閉包中,可是它是一個可變借用。由於子線程極可能比主線程運行時間長,當主線程這邊執行完了代碼, v 極可能會被銷燬,可是閉包中還存有 v 的引用,那這就是一個懸垂指針,這是 Rust 決不能容許發生的事情。
2. v 是可變綁定, 在子線程中的閉包中,被可變借用了一次。可是在主線程尾部代碼的 pop 方法,其實也會對 v 進行可變借用。在 Rust 中,可變借用是獨佔的,也就是說,不能同時對 v 進行兩次或屢次可變借用。不然,v 就會被修改出非預期的值。
看到了嗎?Rust 的全部權機制,完美地在編譯期發現了線程安全問題。
在上面代碼中,Option<T> 是一個枚舉(enum)類型,這實際上是一個代數數據類型中的「和(sum)類型」。
你能夠用「加法原理」來簡單地理解該類型,也就是說,完成一件任務,存在 A 或 B 等多種辦法,選其中一種便可完成該任務。
同理,對於 Option<T> 類型,存在 Some(T) 和 None 兩個值,前者表明「有」,後者表明「無」。也就是說, Option<T> 這個類型,表明一種「可選」類型。
因此,在倒數第三行代碼中, find 方法返回的應該是一個 Option<T> 類型,由於在數組中搜索大於等於42的數,是有可能搜不到的。因此,這個結果會同時存在兩個值,要麼是有值,要麼是沒有值。因此,當你要使用 v 的時候,你要經過模式匹配(match)來判斷,究竟是 Some(T) 仍是 None。這就強制了開發者去處理 None 的狀況,有效避免了空指針。
上面 Result<T, E> 和 Option<T>,是類似的。只不過 Result 是用於錯誤處理,而 Option 用於消除失敗。這也體現了 Rust 錯誤處理的一種精緻的哲學,不像其餘語言那樣只用一個異常就包括了開發過程當中的全部問題。在 Rust 裏,你能夠分狀況用不一樣的類型來處理錯誤。
而且 Rust 經過引入代數數據類型,爲開發者提供了優雅的錯誤處理方法。
Rust 也爲開發者提供了底層內存分配的控制能力。
Rust 默認在棧上存儲數據,可是你也能夠經過 Box<T> 這樣的智能指針顯式地在堆上分配內存。
並且能夠看具體的場景,選擇適合你的全局內存分配器。
你能夠經過泛型來靜態分發,也能經過 trait 對象進行動態分發。
trait 是對 類型 行爲的抽象。它有四個做用:
3. 標籤。好比 Copy 、 Size、Sync、Send等,其中 Copy trait,可讓編譯器來識別哪些類型是能夠安全存儲到棧上的,哪些又必須是堆上,若是在堆上,就能自動Move全部權。
trait 是 Rust 的靈魂。trait 也是 Rust 對 組合優於繼承 理念的一種體現。
由於 Rust 還在成長中,其 AST(抽象語法樹)還在頻繁地變化,因此 Rust 將 Token (分詞)接口 穩定了出來,宏就基於 Token 處理。
Rust 如今支持聲明宏和過程宏,用於知足不一樣的需求,目前過程宏還在不斷地完善和優化,目前還在等程宏屬性宏的穩定。
Rust 本質上能夠分爲「Safe Rust」和 「Unsafe Rust」。
這是由於,如前文所說,咱們整個互聯網的世界就是創建在一個 Unsafe 的基礎上。咱們必須擁抱 Unsafe,才能創建 Safe 的理想世界。
所謂 Safe,其實就是 編譯器對你說:Trust me this time。
所謂 Unsafe,其實就是 你對編譯器和其餘開發者說:Trust me thist time。而且,也意味着,其餘開發者對你說:trust you this time。
不須要對 Unsafe 感到恐懼。雖然如今 Unsafe Rust 還缺少一些開發規範,可是 Rust 團隊還在努力,未來也會經過 Miri 的形式,在Unsafe Rust 中加入 UB 自動檢查,到時候 Unsafe Rust 將更加有保證。
Rust 中還提供了零開銷的 FFI ,能夠輕鬆經過 C-ABI 來訪問任何函數,也能夠經過 C-ABI 來公開 Rust 的函數和類型。
說到這裏,Rust 的特性基本差很少了, Rust 還有不少特性等待你去探索。
只有在瞭解 Rust的特性以後,才能選擇合適的場景去應用它。
在瞭解Rust語言特性以後,第一印象是,Rust 語言是衝着C/C++的應用領域而去的。
但其實也不只僅如此,由於 Rust 語言包含了不少現代語言特性,有高度抽象的表達能力,它其實也適合 Java、python、Andriod 、Swift的應用領域。
再加上如今 WebAssembly 的支持,Rust 也能應用於前端開發。
本質上, Rust是一個真正的全棧語言,適用於嵌入式、操做系統、網絡服務、應用開發、遊戲開發等等領域。
由於C語言當時開創了一個互聯網時代,那麼 Rust 既然擁有C/C++的能力,是否是也能夠開創一個新時代呢?
這裏列出了一些相對比較知名的 Rust 實現的產品。
這些項目或產品,都有一個共同的特色:它們都使用了 Rust 來創造或應對將來。
好比,Goolge 的 Fuchsia 操做系統,它有大概近 50% 的代碼是 Rust 實現的,主要用於網絡層。爲何要從新寫個操做系統?咱們前面說過了,操做系統到了它的瓶頸,無論你有多麼喜歡 Unix 的設計哲學,可是時代在變化。
包括 Redox,是純 Rust 實現的下一代安全的操做系統,背後的商業公司是 System76 ,它的理念是「一切皆 URL」。
TockOS,則是純 Rust 實現的嵌入式實時操做系統,最近已經用於 Google 的加密硬件設備產品中。
Bottlerocket 則是亞馬遜純 Rust 實現的基於 Linux 的專門承載容器的操做系統,專一於安全性和可維護性,提供可靠、一致的服務,以及基於容器的工做負載的安全平臺。這是亞馬遜多年雲平臺產品積累的經驗,由於它們也看到將來安全和性能兼備的重要性。
國產分佈式 New Sql 數據庫 TiDB 的底層分佈式存儲 TiKV,也是 Rust 實現的。還有國產的區塊鏈公鏈項目 NervOS,也是用 Rust 實現的。
還有不少優秀的項目,它們都使用了 Rust。此處就不一一列舉了。
在 Rust 的官方頁面( https://www.rust-lang.org/zh-CN/production/users )上,能夠看到在生產環境中使用 Rust 的公司。上面只是列舉了一部分,其中包括了國內的字節跳動(飛書)、PingCAP(TiKV)、祕猿(NervOS、Cita)三家公司,其實還有知乎、淘寶,也都在使用 Rust。未來你們必定也還會看到更多使用 Rust 的公司。
可是,當前 Rust 也存在一些缺點,阻礙了不少公司想要使用它的決策。
這是流傳最多的一個緣由。Rust 的設計目標和特性,決定了 Rust 是一個進入高門檻的編程語言。可是這並不意味着 Rust 是一個複雜的語言。相反, Rust 是一門簡潔的語言。Rust 語言的設計高度一致性,足以讓開發者抓住它簡潔的內核,創建和 Rust 編譯器相匹配的心智(編程)模型。固然,這須要開發者,下必定的功夫。
2. Rust 編譯慢。這一樣是 Rust 語言設計目標引發的問題,由於 Rust 要在編譯期完成各類安全檢查、展開抽象等功能。幸運的是,Rust 官方團隊也在逐漸改進這個問題。好比支持增量、併發編譯等功能。
3. Rust 生態還在成長過程當中。 好比最使人期待的異步開發功能,才穩定沒多久,生態還在成長,這可能會給某些團隊帶來引入的困難。
4. 有些語言特性還待完善,好比 Const Genric 、特化等。
還有一點須要注意,Rust 並非默認就是高性能的代碼,還須要開發者本身優化。
那麼,當地大學生學習 Rust 有什麼意義呢?難道學習C/Cpp/Java還不夠嗎?
Rust 和 C/ Cpp/ Java的比較前面已經說過了。我認爲, 大學生學習 Rust 的意義有如下幾點?
1. 跟進時代變革的腳步。學習 Rust 能夠緊跟技術的前沿,瞭解相關領域的發展動態。
2. 無阻礙地培養本身的全棧能力。Rust 語言自己就有全棧能力,你掌握了 Rust 語言,就不用把本身束縛到某個領域中,由於 Rust 在操做系統、數據庫、網絡服務、嵌入式、前端開發等等,都有應用,你要切入某個領域,就不須要再多學一門編程語言了,只須要掌握那個領域的領域知識便可。
3. 打造良好的編程基礎和思惟習慣。Rust 是當下惟一一門,抽象表達能力比肩高級動態語言,性能和底層控制能力比肩C/C++的語言,學習 Rust 的過程當中,會把你對於底層操做系統、網絡、範式抽象、設計模式等基礎都訓練一遍。若是你不得不學其餘語言,有了這些基礎,你會很快掌握其餘語言。而且,在 Rust 編譯器的打磨下,你能擁有一個良好的思惟習慣。
4. 成爲更好的開發者。
爲何這麼說呢?由於 Rust 編譯器的存在,可讓你養成:
2. 對內存安全、線程安全等基礎創建一個系統的認知,能夠高效地產出正確的程序。
對於企業來講,那你就是一個合格且更優秀的開發者。
學習 Rust 讓我從新愛上了編程,使用 Rust 讓我感到驕傲和自豪。我也但願能把這門優秀的語言,推廣給更多的人,尤爲是高校的廣大同窗們。
感謝閱讀。
點擊閱讀原文,可直達 B 站視頻回放頁面。