在深刻探索Rust語法的細枝末節以前,先經過一個麻雀雖小但五臟俱全的小項目來總體把握Rust,這樣能夠避免迷失在細節的海洋中。咱們可能會經過這個小項目一會兒接觸到不少新概念,但沒必要驚慌,咱們只需淺嘗輒止對這些概念有個印象便可。git
《TRPL》(Rustaceans對《The Rust Programming Language》的愛稱)中就給出了一個經典的猜謎遊戲的例子,咱們一塊兒來學習它。編程
猜謎遊戲:程序將會隨機生成一個 1 到 100 之間的隨機整數。接着它會請玩家猜一個數並輸入,而後提示猜想是大了仍是小了。若是猜對了,它會打印祝賀信息並退出。安全
Rust如何管理項目
並非全部的代碼都像hello_world.rs
同樣,一個文件就能夠搞定。一個項目每每具備複雜的代碼,咱們須要一種機制來管理這種複雜性,將一個項目切分紅若干小部分,每一個部分再進行切分,層層抽象,直到達到人腦能夠處理的規模。每種編程語言都有這樣的機制,例如Java的package機制,Rust也不例外。編程語言
Rust用了兩個概念來管理項目:一個是crate(項目),一個是mod(模塊)。模塊是用於在crate內部進行分層和封裝的機制,模塊內部能夠包含模塊。函數
- crate:能夠簡單理解爲一個項目,crate是Rust中的獨立編譯單元(compile unit),每一個crate對應生成一個庫或者可執行文件。做爲對比,咱們比較熟悉的C語言中,一個單獨的.c文件和其全部的include文件組成一個編譯單元,每一個.c生成一個.o,而後將這些.o連接起來生成可執行文件。
- mod:能夠簡單理解爲命名空間。mod能夠嵌套(注意crate之間不能出現循環引用),還能夠控制內部元素的可見性。
說到可見性問題,在Rust中,元素默認都是私有的,用pub
關鍵字修飾的纔是公開的。公開和私有的訪問權限規定以下:oop
- 若是一個元素是私有的,那麼只有本模塊內的元素以及它的子模塊能夠訪問
- 若是一個元素是公開的,那麼能夠在本模塊外的做用域訪問它。
模塊是一種抽象的概念,文件是承載這個概念的實體,可是模塊和文件並非簡單的一一對應關係。在一個crate內部建立mod的方式有下面三種:學習
- 在一個rs文件中建立內嵌模塊,直接使用
mod
關鍵字便可。 - 獨立的一個rs文件就是一個模塊,文件名便是模塊名。
- 一個文件夾也能夠建立一個模塊,文件夾內部要有一個
xxx.rs
文件,這個文件是這個模塊的入口。必需要在這個xxx.rs
中聲明其子模塊,不然子模塊沒法被當成這個項目的源碼進行編譯。
新建項目
cargo不只是Rust的包管理器,還能夠用於建立項目。使用下列命令能夠建立一個名爲guessing_game
的項目:優化
cargo new guessing_game --bin
注意,後面的--bin
意味着咱們但願項目生成的是可執行程序,若是但願是library,則可使用--lib
選項。ui
以上爲在命令行中手工建立項目,在Clion中能夠點擊File->New Project
後以下圖填寫,而後點擊Create
按鈕:編碼
項目結構
咱們可使用tree .
命令或者直接在Clion中查看當前的文件夾結構,以下圖所示:
- src/main.rs:這是cargo自動生成的rs文件。還記得前面講的crate和mod的概念嗎?在這個項目中,
guessing_game
是crate,src
文件夾是mod,main.rs
是src
mod的入口。咱們能夠在src
mod中建立子模塊,但注意,這些子模塊都要在main.rs
中聲明,不然沒法參與編譯。 - .gitignore:這是git忽略文件,不懂其做用的同窗能夠自行搜索。這裏重點要說的是經過
cargo new
建立的項目自然就是一個git項目,這也印證了Rust對開源的擁護。 - Cargo.toml:這是項目管理配置文件。TOML是一種很是簡潔好用的配置文件格式,TOML是
Tom's Obvious, Minimal Language
的首字母縮寫,這裏的Tom是Github的聯合創始人之一,感興趣的同窗能夠進一步自行了解TOML配置文件的寫法。 - Cargo.lock:該文件包含項目依賴項的確切信息,由Cargo維護,咱們無須關心它。
編譯執行
在src/main.rs
裏cargo已經自動生成了輸出Hello, world!
的代碼,咱們來運行一下它看可否正常輸出。
插一句題外話,在你往後漫長的Rust編碼生涯中,你會發現,你將在處理編譯錯誤上耗費大量的時間。還記得嗎,Rust 的一大特點是保證內存安全,這保證了Rust代碼只要運行起來就幾乎不會發生內存錯誤,這麼誘人的效果的背後的代價就是,咱們要在編碼時付出額外的努力。Rust爲了保證內存安全設計了一套複雜的規則,這致使咱們的代碼一不留神就會編譯不過。因此,在你往後常常用的一個操做就是檢查可否編譯經過,而不是直接編譯,由於直接編譯還要作代碼優化等會相對費時。可使用下列命令檢查編譯錯誤:
cargo check
在Clion中須要新加一個Configuration來執行cargo check
命令,以下圖所示:
確保cargo check
經過後,能夠執行cargo build
來執行編譯。編譯後會產生一個target
文件夾,在target/debug
下會有一個和crate同名的可執行文件。但通常爲了方便,能夠直接執行cargo run
,這條命令等價於先編譯後執行。下圖是執行cargo run
後Clion的控制檯輸出:
猜謎遊戲
在完成了項目搭建後,接下來就要開始猜謎遊戲的代碼編寫了,我將它們分紅六部分:建立變量、輸入、輸出、錯誤處理、隨機數生成、完整代碼。
建立變量
使用let
語句建立變量,須要注意的是,在Rust中,變量默認是不可變的,能夠在變量名前使用mut
來使得變量可變:
let a = 5; // 不可變 let mut b = 10; // 可變
在上面的let
語句中,咱們並無顯示聲明變量的類型,但這並不表明Rust是動態類型的,Rust仍然是靜態類型的,只不過Rust有一個能夠經過上下文推斷類型的強大編譯器。
輸出
咱們早已在hello_world.rs
中見識過了最基本的輸出方式:
println!("Hello, world!");
須要注意的是,這裏的println!
是一個宏,而非一個函數,println後面的感嘆號就是宏的標誌。Rust中的宏與C/C++中的宏是徹底不同的東西,簡單說,能夠把它理解爲一種安全版的編譯期語法擴展。這裏之因此使用輸出宏而非函數,是由於標準輸出宏能夠完成編譯期格式檢查,更加安全。
若是須要輸出某個變量的值,可使用佔位符{}
,例如:
let x = 0; let y = 10; println!("x = {} and y = {}", x, y);
其輸出結果爲:
x = 0 and y = 10
輸入
爲了從控制檯中獲取用戶的輸入,須要使用標準庫std::io
。使用use
語句將該庫引入當前做用域:
use std::io;
咱們可使用io
庫中的函數stdin
:
let mut guess = String::new(); // 建立一個字符串類型的可變變量 io::stdin().read_line(&mut guess).expect("Failed to read line");
stdin
函數返回一個std::io::Stdin
的實例,這表明終端標準輸入句柄的類型。而後調用read_line
方法,能夠從標準輸入中讀取一行並存入到guess
變量中去。&
表示這是一個引用,這是一個複雜的特性,咱們如今無須瞭解它。
讀取用戶輸入後,咱們須要判斷用戶是否正確輸入了數字。String
類型帶有處理字符串處理的一些方法:
let guess: u32 = guess.trim().parse().expect("Please type a number!");
字符串的 parse
方法將字符串解析成數字。由於這個方法能夠解析多種數字類型,所以須要告訴 Rust 具體的數字類型,這裏經過 let guess: u32
指定。guess
後的冒號:
告訴 Rust 咱們指定了變量的類型。Rust 有一些內建的數字類型,u32 是一個無符號的 32 位整型。trim
方法用於消除回車空格等符號。
錯誤處理
上一小節代碼中還有一個expect
沒有分析,而這就涉及到Rust中的錯誤處理機制了。read_line
的返回值類型是io::Result
,它是Result
類型在io
模塊的特化版本。Result
是枚舉類型,其成員爲Ok
和Err
,Ok
成員表示操做成功,內部包含成功時產生的值。Err
成員則意味着操做失敗,而且包含失敗的來龍去脈。
Result
類型的做用是編碼錯誤處理信息。Result
類型像其餘類型同樣,擁有定義於其上的方法。io::Result
的實例擁有expect
方法。若是 io::Result
實例的值是 Err
,expect
會致使程序崩潰,並打印參數傳遞給 expect
的信息。若是io::Result
實例的值是 Ok
,expect
會獲取 Ok
中的值並返回。在本例中,這個值是用戶輸入到標準輸入中的字節數。
隨機數生成
猜謎遊戲須要可以自動生成隨機數。Rust標準庫中還沒有包含隨機數功能,但咱們能夠經過引入外部crate來得到隨機數功能。還記得Rust的官方開源倉庫嗎,那裏但是有不少寶貝的。打開https://crates.io/
,在搜索框中鍵入rand
來搜索具備隨機數功能的crate,出來的第一個結果就是咱們須要的crate。
如今咱們將這個庫引入到咱們的項目中。打開Cargo.toml
,在[dependencies]
下添加:
[dependencies] rand = "0.8.3"
[dependencies]
告訴 Cargo 本項目依賴了哪些外部 crate 及其版本。
下面使用rand
庫來產生隨機數。首先,使用use
語句引入rand
,use rand::Rng;
。而後調用下列函數產生一個1和100之間的數:
let number = rand::thread_rng().gen_range(1..=100);
完整代碼
猜謎遊戲的完整代碼以下。
use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1..=100); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
其中,涉及控制流操做的loop
、match
、continue
、break
等語法,你們應當可以望文生義。對於這個完整代碼,你們可以閱讀並知道每一行幹了啥便可,沒必要糾結於語法細節。
猜謎遊戲運行結果以下:
參考文獻
- 《The Rust Programming Language》
- 《Rust編程之道》張漢東
- 《深刻淺出Rust》範長春