第1章 與君初相見
Rust編程語言的官方網站是https://www.rust-lang.org/。在官網主頁上,我們可以看到,在最顯眼的位置,寫著Rust語言最重要的特點:
Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.
Rust語言是一門系統編程語言,它有三大特點:運行快、防止段錯誤、保證線程安全。
系統級編程是相對于應用級編程而言。一般來說,系統級編程意味著更底層的位置,它更接近于硬件層次,并為上層的應用軟件提供支持。系統級編程語言一般具有以下特點:
? 可以在資源非常受限的環境下執行;
? 運行時開銷很小,非常高效;
? 很小的運行庫,甚至于沒有;
? 可以允許直接的內存操作。
目前,C和C++應該是業界最流行的系統編程語言。Rust的定位與它們類似,但是增加了安全性。C和C++都是編譯型語言,無須規模龐大的運行時(runtime)支持,也沒有自動內存回收(Garbage Collection)機制。
本章主要對Rust做一個簡單的介紹,準備好一些基本概念以及開發環境。
1.1 版本和發布策略
Rust編程語言是開源的,編譯器的源碼位于https://github.com/rust-lang/rust項目中,語言設計和相關討論位于https://github.com/rust-lang/rfcs項目中。對于想深入研究這門語言的讀者來說,這是一個非常好的消息,大家可以通過研讀開放的源代碼和技術文檔了解到很多書本上沒有講解過的知識。任何一個開發者都可以直接給這個項目提bug,或者直接貢獻代碼。Rust項目是完全由開源社區管理和驅動的,社區的氛圍非常友好。
Rust編譯器的版本號采用了“語義化版本號”(Semantic Versioning)規劃。在這個規則之下,版本格式為:主版本號.次版本號.修訂號。版本號遞增規則如下。
? 主版本號:當你做了不兼容的API修改
? 次版本號:當你做了向下兼容的功能性新增
? 修訂號:當你做了向下兼容的問題修正
Rust的第一個正式版本號是1.0,是2015年5月發布的。從那以后,只要版本沒有出現大規模的不兼容的升級,大版本號就一直維持在“1”,而次版本號會逐步升級。Rust一般以6個星期更新一個正式版本的速度進行迭代。
為了兼顧更新速度以及穩定性,Rust使用了多渠道發布的策略:
? nightly版本
? beta版本
? stable版本
nightly版本是每天在主版本上自動創建出來的版本,這個版本上的功能最多,更新最快,但是某些功能存在問題的可能性也更大。因為新功能會首先在這個版本上開啟,供用戶試用。beta版本是每隔一段時間,將一些在nightly版本中驗證過的功能開放給用戶使用。它可以被看作stable版本的“預發布”版本。而stable版本則是正式版,它每隔6個星期發布一個新版本,一些實驗性質的新功能在此版本上無法使用。它也是最穩定、最可靠的版本。stable版本是保證向前兼容的。
在nightly版本中使用試驗性質的功能,必須手動開啟feature gate。也就是說要在當前項目的入口文件中加入一條#! [feature(…name…)]語句。否則是編譯不過的。等到這個功能最終被穩定了,再用新版編譯器編譯的時候,它會警告你這個feature gate現在是多余的了,可以去掉了。
Rust語言相對重大的設計,必須經過RFC(Request For Comments)設計步驟。這個步驟主要是用于討論如何“設計”語言。這個項目存在于https://github.com/rust-lang/rfcs。所有大功能必須先寫好設計文檔,講清楚設計的目標、實現方式、優缺點等,讓整個社區參與討論,然后由“核心組”(Core Team)的成員參與定奪是否接受這個設計。筆者強烈建議各位讀者多讀一下RFC文檔,許多深層次的設計思想問題可以在這個項目中找到答案。在Rust社區,我們不僅可以看到最終的設計結果,還能看到每一步設計的過程,對我們來說非常有教育意義。
Rust語言每個相對復雜一點的新功能,都要經歷如下步驟才算真正穩定可用:
RFC→Nightly→Beta→Stable
先編寫一份RFC,其中包括這個功能的目的、詳細設計方案、優缺點探討等。如果這個RFC被接受了,下一步就是在編譯器中實現這個功能,在nightly版本中開啟。經過幾個星期甚至幾個月的試用之后,根據反饋結果來決定撤銷、修改或者接受這個功能。如果表現不錯,它就會進入beta版本,繼續過幾個星期后,如果確實沒發現什么問題,最終會進入stable版本。至此,這個功能才會被官方正式定為“穩定的”功能,在后續版本中要確保兼容性的。
這個發布策略非常成功,它保證了新功能可以持續、快速地進入到編譯器中。在這個發布策略的支持下,Rust語言以及編譯器的進化速度非常了不起,成功實踐了快速迭代、敏捷交付以及重視用戶反饋的特點,同時也保證了核心設計的穩定性——用戶可以根據自己的需要和風險偏好,選擇合適的版本。本書假定讀者安裝的是nightly版本,因為我們的目標是學習,目前有許多重要的功能只存在于nightly版本。
在2017年下半年,Rust設計組又提出了一個基于epoch的演進策略(后來也被稱為edition)。它要解決的問題是,如何讓Rust更平穩地進化。比如,有時某些新功能確實需要一定程度上破壞兼容性。為了最大化地減少這些變動給用戶帶來的影響,Rust設計組又設計了一個所謂的edition的方案。簡單來說就是讓Rust的兼容性保證是一個有時限的長度,而不是永久。Rust設計組很可能會在不久的將來發布一個2018 edition,把之前的版本叫作2015 edition。在這個版本的進化過程中,就可以實施一些不兼容的改變。當然了,Rust設計組不會突然讓前一個edition的代碼到了后一個edition就不能編譯了。他們采用了一種平滑過渡的方案。
我們舉個例子。假設我們要添加一個功能,比如增加一個關鍵字。這件事情肯定是不兼容的改變,因為用戶寫的代碼中很可能包含用這個關鍵字命名的變量、函數、類型等,直接把這個單詞改成關鍵字會直接導致這些遺留代碼出現編譯錯誤。那怎么辦呢?首先會在下一個edition中做出警告,提示用戶這個單詞已經不適合作為變量名了,請用戶修改。但是這個階段代碼依然能編譯通過。然后到再下一個edition的時候,這個警告就會變成真正的編譯錯誤,此時這個關鍵字就可以真正啟用了。先編譯警告,再編譯錯誤,這個過程可能會持續好幾年,所以Rust的穩定性還是基本上有保證的。畢竟,如果要維持百分之百的兼容性,Rust語言就很難再繼續進化了。如果讓極少一部分受影響的遺留代碼,完全鎖死整個語言的進步空間,對于那些特別需要某些新功能的用戶來說也是不公平的。通過這個緩慢過渡的策略,基本可以讓所有Rust的使用者平滑、無痛地過渡到新版本。幾年的過渡時間也是足夠充分的。
Rust的標準庫文檔位于https://doc.rust-lang.org/std/。學會查閱標準庫文檔,是每個Rust使用者的必備技能之一。
1.2 安裝開發環境
Rust編譯器的下載和安裝方法在官網上有文檔說明,點擊官網上的Install鏈接可以查看。Rust官方已經提供了預編譯好的編譯器供我們下載,支持Windows平臺、Linux平臺以及Mac平臺。但是一般我們不單獨下載Rust的編譯器,而是使用一個叫rustup的工具安裝Rust相關的一整套工具鏈,包括編譯器、標準庫、cargo等。使用這個工具,我們還可以輕易地更新版本、切換渠道、多工具鏈管理等。
在官網上下載rustup-init程序,打開命令行工具,執行這個程序,按照提示選擇合適的選項即可。不論在Windows、Linux還是Mac操作系統上,安裝步驟都是差不多的。
在Windows平臺下的選項要稍微麻煩一點。在Windows平臺上,Rust支持兩種形式的ABI(Application Binary Interface),一種是原生的MSVC版本,另一種是GNU版本。如果你需要跟MSVC生成的庫打交道,就選擇MSVC版本;如果你需要跟MinGW生成的庫打交道,就選擇GNU版本。一般情況下,我們選擇MSVC版本。在這種情況下,Rust編譯器還需要依賴MSVC提供的鏈接器,因此還需要下載VisualC++的工具鏈。到Visual Studio官網下載VS2015或者VS2017社區版,安裝C++開發工具即可。
安裝完成之后,在$HOME/.cargo/bin文件夾下可以看到一系列的可執行程序,比如Rust 1.19版本的時候,在Windows平臺上安裝的程序如圖1-1所示。

圖1-1
其中,rustc.exe是編譯器,cargo.exe是包管理器,cargo-fmt.exe和rustfmt.exe是源代碼格式化工具,rust-gdb.exe和rust-lldb.exe是調試器,rustdoc.exe是文檔生成器,rls.exe和racer.exe是為編輯器準備的代碼提示工具,rustup.exe是管理這套工具鏈下載更新的工具。
我們可以使用rustup工具管理工具鏈。
// 更新rustup本身 $ rustup self update // 卸載rust所有程序 $ rustup self uninstall // 更新工具鏈 $ rustup update
我們還可以使用它輕松地在stable/beta/nightly渠道中切換,比如:
// 安裝nightly版本的編譯工具鏈 $ rustup install nightly // 設置默認工具鏈是nightly版本 $ rustup default nightly
為了提高訪問速度,中國科技大學Linux用戶協會(USTC LUG)提供了一個代理服務,官方網址為https://lug.ustc.edu.cn/wiki/mirrors/help/rust-static,建議國內用戶設置好以下環境變量再使用rustup:
export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
Rust官方工具鏈還提供了重要的包管理工具cargo.exe,我們可以通過這個工具輕松導入或者發布開源庫。官方的管理倉庫在https://crates.io/,大家可以登錄這個網站瀏覽一下Rust社區熱門的開源庫都有哪些。大型項目往往需要依賴這些開源庫,cargo會幫我們自動下載編譯。同樣,為了解決網絡問題,需要利用USTC提供的代理服務,使用方式為:在$HOME/. cargo目錄下創建一個名為config的文本文件,其內容為:
[source.crates-io] registry = "https://github.com/rust-lang/crates.io-index" replace-with = 'ustc' [source.ustc] registry = "git://mirrors.ustc.edu.cn/crates.io-index"
這樣,在編譯需要依賴crates.io的項目時,不會由于網絡問題導致依賴庫下載失敗。
RLS(Rust Language Server)是官方提供的一個標準化的編輯器增強工具。它也是開源的,項目地址在https://github.com/rust-lang-nursery/rls。它是一個單獨的進程,通過進程間通信給編輯器或者集成開發環境提供一些信息,實現比較復雜的功能,比如代碼自動提示、跳轉到定義、顯示函數簽名等。安裝最新的RLS的方法為:
// 更新rustup到最新 rustup self update // 更新rust編譯器到最新的nightly版本 rustup update nightly // 安裝RLS rustup component add rls --toolchain nightly rustup component add rust-analysis --toolchain nightly rustup component add rust-src --toolchain nightly
有了這些準備,大家就可以在Visual Studio Code中下載支持Rust的插件,提升編輯體驗。理論上來說,RLS可以跟任何編輯器或者集成開發環境配合使用,只要這個編輯器實現了它們之間的通信協議即可。
有了上面這些準備工作,我們就可以正式開始Rust編程之旅了。首先,打開命令行工具,看看rustc編譯器能否正常運行,使用-V命令查看rustc的版本:
$ rustc -V rustc 1.20.0-nightly (f85579d4a 2017-07-12)
如果看到類似的輸出,說明編譯器已經可以正常工作。接下來,請大家探索一下這些工具的簡明使用幫助:
1)使用rustc -h命令查看rustc的基本用法;
2)使用cargo -h命令查看cargo的基本用法;
3)使用rustc -C help命令查看rustc的一些跟代碼生成相關的選項;
4)使用rustc -W help命令查看rustc的一些跟代碼警告相關的選項;
5)使用rustc -Z help命令查看rustc的一些跟編譯器內部實現相關的選項;
6)使用rustc -help -V命令查看rustc的更詳細的選項說明。
1.3 Hello World
編程語言入門第一課,必須得是hello world程序。我們先來看看Rust的hello world是什么樣子:
// hello_world.rs fn main() { let s = "hello world! "; println!("{}", s); }
對于這樣一個簡單的示例程序,我們并沒有使用cargo創建工程,因為沒有復雜的依賴關系。編譯就直接使用rustc即可,其他所有選項使用默認值:
rustc hello_world.rs
可看到本地文件夾中生成了一個名為hello_world的可執行程序。執行./hello_world程序,可以看見控制臺上輸出了hello world!字符串。恭喜讀者,第一個Rust程序已經運行成功了!
我們來分析一下這個最簡單的程序。
1)一般Rust源代碼的后綴名使用.rs表示。源碼一定要注意使用utf-8編碼。
2)第一行是注釋語句,Rust的注釋是C語言系列風格的,行注釋采用//開頭,塊注釋使用/*和*/包圍。它還支持更高級的文檔注釋,將在后文中詳細展開說明。
3)fn是一個關鍵字(key word),函數定義必須以這個關鍵字開頭。函數體使用大括號來包含。fn是單詞function的縮寫,在Rust中,設計者比較偏向使用單詞縮寫,即使是關鍵字也不例外。在代碼風格上,某些讀者可能開始會有點不習慣。但總體而言,這只是個審美偏好而已,不必過于糾結,習慣就好。
4)默認情況下,main函數是可執行程序的入口點,它是一個無參數,無返回值的函數。如果我們要定義的函數有參數和返回值,可以使用以下語法(參數列表使用逗號分開,冒號后面是類型,返回值類型使用->符號分隔):
fn Foo( input1 : i32, input2 : u32) -> i32 { ... }
5)局部變量聲明使用let關鍵字開頭,用雙引號包含起來的部分是字符串常量。Rust是靜態強類型語言,所有的變量都有嚴格的編譯期語法檢查。關于Rust的變量和類型系統將在后文詳細說明。
6)每條語句使用分號結尾。語句塊使用大括號。空格、換行和縮進不是語法規則的一部分。這都是明顯的C語言系列的風格。
最簡單的標準輸出是使用println!宏來完成。請大家一定注意println后面的感嘆號,它代表這是一個宏,而不是一個函數。Rust中的宏與C/C++中的宏是完全不一樣的東西。簡單點說,可以把它理解為一種安全版的編譯期語法擴展。這里之所以使用宏,而不是函數,是因為標準輸出宏可以完成編譯期格式檢查,更加安全。
從這個小程序的驚鴻一瞥中,大家可以看到,Rust的語法主要還是C系列的語法風格。對于熟悉C / C++ / Java / C# / PHP / JavaScript等語言的讀者來說,會看到許多熟悉的身影。
1.4 Prelude
Rust的代碼從邏輯上是分crate和mod管理的。所謂crate大家可以理解為“項目”。每個crate是一個完整的編譯單元,它可以生成為一個lib或者exe可執行文件。而在crate內部,則是由mod這個概念管理的,所謂mod大家可以理解為namespace。我們可以使用use語句把其他模塊中的內容引入到當前模塊中來。關于Rust模塊系統的詳細說明,可參見本書第五部分。
Rust有一個極簡標準庫,叫作std,除了極少數嵌入式系統下無法使用標準庫之外,絕大部分情況下,我們都需要用到標準庫里面的東西。為了給大家減少麻煩,Rust編譯器對標準庫有特殊處理。默認情況下,用戶不需要手動添加對標準庫的依賴,編譯器會自動引入對標準庫的依賴。除此之外,標準庫中的某些type、trait、function、macro等實在是太常用了。每次都寫use語句確實非常無聊,因此標準庫提供了一個std::prelude模塊,在這個模塊中導出了一些最常見的類型、trait等東西,編譯器會為用戶寫的每個crate自動插入一句話:
use std::prelude::*;
這樣,標準庫里面的這些最重要的類型、trait等名字就可以直接使用,而無須每次都寫全稱或者use語句。
Prelude模塊的源碼在src/libstd/prelude/文件夾下。我們可以看到,目前的mod.rs中,直接導出了v1模塊中的內容,而v1.rs中,則是編譯器為我們自動導入的相關trait和類型。
1.5 Format格式詳細說明
在后面的內容中,我們還會大量使用println!宏,因此提前介紹一下這個宏的基本用法。跟C語言的printf函數類似,這個宏也支持各種格式控制,示例如下:
fn main() { println!("{}", 1); // 默認用法,打印Display println!("{:o}", 9); // 八進制 println!("{:x}", 255); // 十六進制 小寫 println!("{:X}", 255); // 十六進制 大寫 println!("{:p}", &0); // 指針 println!("{:b}", 15); // 二進制 println!("{:e}", 10000f32); // 科學計數(小寫)
println!("{:E}", 10000f32); // 科學計數(大寫) println!("{:? }", "test"); // 打印Debug println!("{:#? }", ("test1", "test2")); // 帶換行和縮進的Debug打印 println!("{a} {b} {b}", a = "x", b = "y"); // 命名參數 }
Rust中還有一系列的宏,都是用的同樣的格式控制規則,如format! write! writeln!等。詳細文檔可以參見標準庫文檔中std::fmt模塊中的說明。
Rust標準庫中之所以設計了這么一個宏來做標準輸出,主要是為了更好地錯誤檢查。大家可以試試,如果出現參數個數、格式等各種原因不匹配會直接導致編譯錯誤。而函數則不具備字符串格式化的靜態檢查功能,如果出現了不匹配的情況,只能是運行期錯誤。這個宏最終還是調用了std::io模塊內提供的一些函數來完成的。如果用戶需要更精細地控制標準輸出操作,也可以直接調用標準庫來完成。
- Angular UI Development with PrimeNG
- Java異步編程實戰
- Learning Chef
- Java持續交付
- Java Fundamentals
- C語言程序設計習題與實驗指導
- 區塊鏈國產化實踐指南:基于Fabric 2.0
- Getting Started with Electronic Projects
- Android應用開發攻略
- Effective C++:改善程序與設計的55個具體做法(第三版)中文版(雙色)
- JavaScript設計模式與開發實踐
- Cloud Development andDeployment with CloudBees
- Learning ArcGIS Geodatabases
- ROS Robotics By Example
- 高并發系統:設計原理與實踐