Rust 程式設計語言學習指導方針。
導言#
最要緊的,我知道,這個《Rust 程式設計語言學習指導大綱》的名字聽起來過於高大上了。但請放心,取這個名字的唯一原因,就是讓其顯得大氣,於是乎您拿著這個小冊子走到公共場合時,(或許)能獲得很高的回頭率,同時說不定也能提高您的學習熱情。
Rust 誕生於 2015 年,距今已經過去了 9 年,是一個已經有一定歷史的程式設計語言。如果您上網去搜索有關 Rust 的詞條,那麼您會看到各種各樣的聲音,評論說 Rust 是一門除了難學以外什麼都好的語言。的確,有人評價 Rust 除了難學以外,具有其他所有的程式語言優秀特質。它的內存安全、並發安全、執行效率等都做到了無與倫比的程度;而且配合有高效或現代的 Cargo 包管理器,以及一整套符合 C 標準的編譯工具鏈(Rust 官方編譯器目前仍然基於 gcc GNU C Compilers)。它的全靜態編譯模式使得從開發到調試都特別簡單;而且作為一門來自開源社區、面向開源社區的語言,這些特性讓它更加適合大規模的基於版本控制系統(在 Rust Cargo 中默認是 Git)的合作開發。Rust 經過了 9 年的發展,雖然是一門較新的語言,但已經獲得了相當多的認可,這其中包括大名鼎鼎的諸多企業,以及聞名遐邇的開源開發者(譬如 Linux 內核開發者 Linus Torvalds 已經著手準備向內核中添加 Rust 以逐漸代替部分 C)。上述名詞您可能一個也聽不懂,但請放心,本自然段概括的主旨是:Rust 絕對是一門好的語言。
“可是 Rust 它難學呀。我難道不應該像網上推薦的一樣學會了 C 再來學嗎?” 的確,如果您曾經接觸過 C 語言或 C ++, 那麼您學習 Rust 的時候將輕鬆很多,且這本小冊子的第一章(《認識計算機》)您應該可以略過不看。然而之所以有學習 C/C++ 來幫助學習 Rust 這樣的說法存在,是因為 Rust 的諸多項特性恰恰是為了解決 C/C++ 以及其他一些編譯型的、略貼近底層的語言的一些詬病而提出的;而說 Rust 難學也恰恰因為這一點 —— 如果從未接觸過底層編程的人來學習 Rust 會感到有很多特性是完全沒有必要且無厘頭的 —— 其實這是因為如果未接觸過編程,或者只接觸過頂層的編程(Javascript, Python 甚至 Scratch),對於計算機的理解是完全不到位的。而這本小冊子將創新性地拒絕一上來就教讀者學習 Hello World 怎麼打印(其實讀者稍後就會知道 Hello World 根本不需要寫,因為 Rust 的新項目默認模板就是 Hello World),而是通過 1000 字左右的圖文並茂的形式,將計算機工作原理中最重要的部分,簡明扼要地傳達給讀者。同時在後續章節中也時時注意從原理入手。這樣一來,即使從未學習過其他任何程式語言,憑藉對於計算機的進一步了解,也能體認到 Rust 的卓越設計,獲得更好的學習效果。
您可能還會有疑問,“為什麼要學這一門我甚至都沒聽說過的語言呢?” 的確,Rust 因為難度大、應用場景相對較窄,被很多初學者所避之不及,並且認為像 Python, Javascript 這樣的語言就完全足夠滿足日常需求,甚至可以用來謀生計。固然他們的說法沒有錯,但您需要意識到,Javascript 是一個面向萬維網的程式語言,並且它最大的特徵就是極其易學;如果您學完了 Rust,那麼 Javascript 對於您來說幾乎是不用學的。這裡沒有貶低任何這類腳本語言的意思,但事實就是這些更高級(我們剛剛說的這些高級與底層都是針對與硬件的距離來說的)的語言誰都可以學,誰都學得會,而且是高度逐新、高度依賴框架的,他們能提供的無論是就業市場還是開源世界裡的地位的廣度和高度都是非常有限的。且不了解當代計算機的工作原理,只是處理業務邏輯(Javascript 的重點),一旦出現底層的問題,也沒有辦法自己排查,還是會依賴於人。底層的事務不會因為語言高級就不存在了;相反,底層的業務總要有人來處理,這就是那些解釋器和解釋器開發者的任務;他們才能成為整個行業的中心。一些底層特性,比如指針,只是解釋器像保姆一樣幫你把它們封裝、管理起來了;無論是效率還是靈活度,肯定比不上真人自己動手的。如果通過學習 Rust, 您能夠對計算機有更加理性和準確的認識,那麼無論是在生活還是在生產中遇到的很多問題都能用明晰的頭腦解決,且學習其他語言也將更加容易;並且說不定您還能成為處理底層業務的大師,成為行業領先的人物。
此外,Rust 並不像很多人說的那樣是用來 “寫驅動程序和操作系統的”。相反,當今已經有非常多的企業將 Rust 應用於生產環境,用來運行它們網絡伺服器和其他數字基礎設施。因為 Rust 具有兩個重要特徵:零成本抽象帶來的高性能、現代內存和並發管理帶來的運行安全。但如果不了解計算機基本原理,並且還像之前所說的那樣,把腳本語言的解釋器當作自己的保姆,那麼這兩點關乎整個計算機時代的穩定性的因素,將永遠無法理解。
認識計算機#
本章需要的前置知識:基本的生活經驗。如果遇到任何不懂的術語,且文中沒有給出相應的解釋,請直接跳過;這些術語對理解本章和本冊子(暫時)沒有幫助和理解的必要。
現代計算機看起來是一個高度複雜的設備;當然也確實如此。一台普通筆記本裡的結構估計是人類目前為止製造的集成度最高的結構了。但無論如何,計算機都是一台機器,而且完全由人設計。科學家們不是沒有考慮過將計算機設計得跟人腦一樣複雜,以期實現和人腦一樣複雜與巧妙的功能;早在上個世紀中期就已經有一群科學家嘗試過了,但受限於當時的(和現在的)生理學認識的有限,連人腦最概括的原理圖都是很難明確的。所以以馮・諾伊曼為首的一群科學家選擇了另一條路,即設計一套超級簡單的計算體系架構;畢竟只要能達成造一台通用計算機的目的就行了,也不要追求人腦級的複雜。於是乎,就誕生了 “超級簡單” 的馮・諾伊曼架構。
好吧,可能也沒有 “那麼簡單”。但大綱是很清楚的。馮・諾伊曼架構定義了過去和現在,以及未來的一切通用計算機的基本架構:運算器 (CPU)、輸入輸出 (I/O Bridge)、存儲器 (Memory)。簡單地說來,運算器是計算的部分、輸入輸出就是字面意思、而(存儲器)內存就是負責存儲東西的地方。千萬不要鬧笑話了,存儲器是 RAM,也是一般大家說的 “內存條”。 而經常被搞混的是硬碟;硬碟屬於輸入輸出設備中的一種!內存又稱易失性存儲或者 RAM ,斷電後裡面所有東西都會清空(要不然為什麼叫易失性存儲),但讀取和寫入速度非常快,而大小一般很小,一般(絕大多數情況下)不會用來存儲文件。硬碟,是連接在上面那個圖表中 “Bus”(常說的總線)的部分的一個輸入輸出設備,用來存放輸入和輸出的數據。內存和硬碟中雖然都可以存儲東西,但最大的不同就是,硬碟是用來接受 CPU 和內存這個 “工廠” 的輸出的結果,並提供輸入進去的材料的設備,它就像是工廠的倉庫;內存則是這個工廠裡面的桌子椅子,給工作人員和各種工具提供暫時的存放空間、以及給待加工的材料提供一個台面來加工。為了避免混淆,這本小冊子中所有的 “內存” 全部都使用 “主存儲器” 來表達,而本冊同時將會使用 “運行數據” 來稱呼所有存在於易失性存儲中的數據和指令,請讀者注意。同時,工廠的比喻將會在本冊貫穿,稍後您會知道原因。
而在 CPU 內部仍然有具備存儲功能的組件。全宇宙適用的機械原理決定了存儲設備的一個特性:容量越小、速度越快(忽略技術原理差異的語境下)。所以 CPU 內部具有的這些存儲組件容量都非常小,最多不會超過幾個 MB(當然了,這種大小已經是現代 CPU 提供的了;一些較舊的教材可能說 CPU 中的存儲組件大小只有幾個字節)。還是工廠,如果說主存儲器是加工台,可以把待加工的材料放在上面;那麼這些 CPU 內部的存儲組件就是工具箱,在極短時間內可以存放一些小零件,並且放在很 “順手” 的地方,可以很快取過來。把東西放到加工台上(從主存儲器中讀取和寫入)需要的時間,要比從工具箱裡拿零件(從 CPU 內部存儲讀取和寫入)的時間還要長好幾十倍,儘管前者已經只有不到半毫秒。
這些 CPU 內部的存儲組件,有它們自己的名字。一些叫作 “高速緩存” (cache),一些叫作 “寄存器” (register), 但讀者不需要了解它們。一些教科書可能會說,編程的時候可以讓程序要求將各類數據存在緩存、寄存器還是主存儲器中 —— 請不要相信和實踐,因為現代編程語言和現代操作系統,出於安全和穩定的考量,都会無視程序的這類要求。Rust, 在編譯時就不會給任何程序選擇運行時數據存放位置的權利。所以我們也不會繼續介紹和了解 CPU 內部的存儲組件;讀者只要知道有這回事就行了。對於程序而言,它們能看到的部分就只有單純提供運算的 CPU, 單純提供運行數據存儲的主存儲器和其他 I/O 設備(包括硬碟,打印機,攝像頭,麥克風,揚聲器...)。就好像工廠經理(操作系統)會告訴工人每個東西應該放在哪裡;不會由著他們亂放。
CPU 提供運算的原理是非常複雜的。但大家總之知道它能夠完成基本四則運算和其他一系列高級的函數等等;這些就是數學和數字電路設計的範疇了,我們雖然要貼近硬件,但也尚且不至於這樣貼近。且筆者能力有限,這一部分就不過多贅述了。
I/O Bridge, 本文稱為輸入輸出,或者 I/O 橋,連接了 I/O 總線和 CPU 以及主存儲器,就像是工廠裡的材料輸入口和產品輸出口。沒有 I/O 橋,這個工廠的存在也毫無意義。I/O 總線聽起來可能有點奇怪,但它的英文名很好地解釋了一切,“Bus”,大巴車。的確,I/O 總線就像大巴車,載著數據從它們的處所(這就有很多可能了,有可能是另一個工廠的輸出,也有可能是一個專門的設備)到工廠去;或者反過來。I/O 這個名字同時也會用得非常廣泛,畢竟就是 input /output 的意思 (輸入 / 輸出)。每個工廠有進料口和輸出口;那每台機器(程序)也可以有進料口和輸出口。所以如果在編程時看到 I/O 用來形容用戶的輸入和顯示在螢幕上的時候,不需要驚訝。
於是乎,我們的計算機系統漫遊就結束了。這部分的內容細細講來可以寫成一本巨作(《深入理解計算機系統》推薦您閱讀,如果想要深入理解計算機系統的話),但對於面向程序的學習來講,這些已經綽綽有餘啦。
P.S. 為什麼將計算機比作工廠、程序比作機器的比喻這麼常見,並且本文也會堅持使用呢?這是因為現代計算機的全名其實叫作通用計算機。換句話說,就是像孫悟空一樣能夠七十二變(而且還是同時變),可以變化成各種專用目的的機器 —— 其實就是在通用計算機誕生或在某特定領域投入使用之前存在的專用計算機(一般叫作專用設備,IC(集成電路)等等)。譬如,交換機就是典型的專用計算機,它不可編程,不可當成別的東西使用;但個人電腦就是通用計算機,您可以通過編程或者安裝已有的程序把它變成電話、變成電子郵件收發機、變成交換機、變成路由器、變成伺服器…… 隨著通用計算機技術的成熟,以及不再需要像專用計算機一樣每次都設計專門的集成電路,同時還為了方便地實現更多功能,越來越多的專用計算機設備已經被通用計算機替代。譬如,物聯網設備如聯網熱水器,就通過一塊單片電路板的通用計算機(單片機)進行控制;從某種意義上來講,這種設備和個人電腦本質上是相同的!這樣的比喻能夠合理地成立,是因為它某種意義上描述了通用計算機誕生之前的世界 —— 也是大多數人所熟知且本能地能接受的世界。
下一章我們將會學習保留節目:用 Rust 寫 Hello World. (但其實我們要寫一個簡單的字符串搜索小程序 —— 不要緊張,這很容易做到的。)