1. 程式人生 > 實用技巧 >Rust中的變數的宣告和定義

Rust中的變數的宣告和定義

變數的宣告和定義

Rust中合法的識別符號(包括變數名、函式名、triat名等)必須由數字、字母、下劃線組成,而且不能以數字開頭。這個和很多語言都是一樣的。Rust將來也會允許其他Unicode字元作為識別符號,還有raw identifier功能,這樣可以使關鍵字作為識別符號,比如r#self,這個用途在FFI中最多。

變數的宣告: let variable : i32 = 100; , 在rust中採用的變數的宣告方式不同於以往語言的宣告方式,這裡先看變數的宣告, 變數的宣告時,是變數的名字先在前,而變數的型別在後面,let variable: i32;就是這個樣子。

這樣變數宣告的好處是,對於語法分析來說分析更為方便,並且在變數宣告語句中最為重要的是變數的名字,將變數名提前突出顯式變數名的重要性,對於型別是變數名的附加說明,可以通過上下文推匯出變數的型別,當讓rust的自動型別推導具有侷限性,對於不能推匯出來的型別,需要手動新增型別說明。

變數宣告中的let的使用也是借鑑了函式式語言的思想,let表明的是繫結的含義,表示的是將變數名和記憶體作了一層繫結關係。在Rust中,一般把宣告的區域性變數並初始化的語句稱為”變數繫結“, 這裡強調的是”繫結“的含義,這裡和C++/C中的”賦值初始化語句有所不同。

變數定義的一些問題

Rust中,每個變數必須被合理初始化之後才能被使用,使用未初始化變數這樣的錯誤,在rust中是不可能出現的。

檢查是否宣告初始化變數

剛剛上面的let variable : i32;這個是宣告,而沒有給變數賦值,這個在別的語言中可能是行的通的,但是在rust中,編譯器直接報錯(如果在後面使用這個為賦值(定義)的變數, Rust編譯器會對程式碼作基本的靜態分支流程分析,確保變數在使用之前一定被初始化,variable沒有繫結任何值,這樣的程式碼會引起很多記憶體不安全的問題,比如計算結果非預期、程式崩潰,所以Rust編譯器必須報錯。

1 let variable: i32;
2 println!("variable  = {}", variable); // error[E0381]: use of possibly unintialized 'variable' 

檢測分支流程是否產生為初始化變數

Rust編譯器的靜態分支流程分析比較嚴格。

1 fn main() {
2     let x: i32;
3     if true {
4         x = 1;
5     } else {
6         x = 2;
7     }
8     println!("x = {}", x);
9 }

這裡的if分支的所有情況都給變數x綁定了值,所以它可以執行。但是如果去掉else分支,編譯器就會報錯:

error: use of possibly unintialized variable : 'x'
println!("x = {}", x);

從這裡可以看到編譯器已經檢查出來變數x沒有被正確的初始化。去掉else分支後,編譯器的靜態分支流程分析判斷出在if表示式之外的println!也用到了變數x,但並未有繫結任何值得行為。編譯器的靜態分支流程分析並不能識別if表示式中的條件是true, 所以他要檢查所有分支的情況。(這個在程式設計語言領域中有專門去做研究的,比如軟體的靜態分析,一些參考材料:南京大學的軟體分析課程)

要是在把println!語句也去掉的話,則可以正常編譯執行,這是因為if表示式之外再也沒有任何地方使用變數x, 在唯一使用到x的if表示式中已經綁定了值,所以編譯正常。

 1 // 有一個例子
 2 fn test(condition: bool ){
 3     let x: i32; //宣告x
 4     if condition {
 5         x = 1; //初始化x,這裡是初始化
 6         println!("{}", x); 
 7     }
 8     // 如果條件不滿足,x沒有被初始化
 9 
10     //但是沒有關係,只要這裡不使用x就沒有事
11 }

檢測迴圈中是否產生未初始化變數

當迴圈中使用break關鍵字的時候,break會將分支中的變數值返回。

 1 fn main() {
 2     let x : i32;
 3     loop {
 4         if true {
 5             x = 2;
 6             break;
 7         }
 8     }
 9     println!("{}", x);// 2
10 }

Rust編譯器的靜態分支流程分析知道,break會將x的值返回,這樣loop迴圈之外println!可以正常列印x的值

空陣列或向量可以初始化變數

當變數繫結空的陣列或向量時,需要顯式制定型別,否則編譯器無法推斷其型別。

1 fn main() {
2     let a: Vec<i32> = vec![];
3     let b: [i32; 0] = [];
4 }

要是不加顯式型別標註的話,編譯器就會報錯: error[e0282]: type annotation needed 空陣列或向量可以用來初始化變數,但目前暫時無法用於初始化常量或靜態變數。

轉移了所有權產生了未初始化變數

當將一個已經初始化的變數y繫結給另外一個變數y2時,Rust會把y看做邏輯上的未初始化變數。 這裡的y和y2都是移動語義的變數,移動語義的變數會發生所有權的交接,而值語義,就像其他C++語言預設的都是傳值。

 1 fn main() {
 2     let x = 42; //原生型別都是值語義,預設儲存在棧上,
 3     let y = Box::new(4); //變數是由Box裝箱到堆上的, Box::new方法在堆上分配記憶體返回指標
 4     //並與y繫結,而指標y儲存在棧上,
 5     println!("{}", y);
 6     let x2 = x;
 7     let y2 = y;
 8     //println!("{}", y);//發生了所有權的轉移,所以這裡的變數y可以看做沒有未被初始化的變數
 9     //但是如果重新給變數繫結上一個值,變數y依然是可用的,這個過程叫做重新初始化
10 }