理解Rust中的變量賦值

Rust中使用let聲明變量

fn main(){
  // 聲明變量name並初始化賦值
  let name = "junmajinlong.com";
  println!("{}", name);  // println!()格式化輸出數據
}

Rust會對未使用的變量發出警告信息。如果確實想保留從未被使用過的變量,可在變量名前加上_前綴。

fn main(){
  let name = "junmajinlong.com";
  println!("{}", name);

  let gender = "male";   // 警告,gender未使用
  let _age = 18;     // 加_前綴的變量不被警告
}

Rust允許聲明未被初始化(即未被賦值)的變量,但不允許使用未被賦值的變量。多數情況下,都是聲明的時候直接初始化的。

fn main() {
  let name;  // 只聲明,未初始化
  // println!("{}", name);  // 取消該行註釋,將編譯錯誤
  
  name = "junmajinlong.com";
  println!("{}", name);
}

Rust允許重複聲明同名變量,後聲明的變量將遮蓋(shadow)前面已聲明的變量。需注意的是,遮蓋不是覆蓋,被遮蓋的變量仍然存在,而如果是被覆蓋則不再存在(也即,覆蓋時,原數據會被銷燬)。

fn main() {
  let name = "junmajinlong.com";
  // 註釋下行,將警告:name變量未被使用
  // 因為name仍然存在,只是被遮蓋了
  println!("{}", name);  
  
  let name = "gaoxiaofang.com";  // 遮蓋已聲明的name變量
  println!("{}", name);
}

變量遮蓋示意圖:

注:下圖內存佈局並不完全正確,此圖僅為說明變量遮蓋
         +---------+       +--------------------+
         |  Stack  |       |        Heap        |
         +---------+       +--------------------+
name --> | 0x56789 |  ---> | "gaoxiaofang.com"  |
         |         |       +--------------------+
name --> | 0x01234 |  ---> | "junmajinlong.com" |
         +---------+       +--------------------+

變量初始化後,默認不允許再修改該變量。注意,修改變量是直接給變量賦值,而不是再次let聲明該變量,再次聲明變量是允許的,它會遮蓋原變量。

fn main() {
  let name = "junmajinlong.com";
  // 取消下行註釋將編譯錯誤,默認不允許修改變量
  // name = "gaoxiaofang.com";
  
  let name = "gaoxiaofang.com";  // 再次聲明變量,遮蓋變量
  println!("{}", name);
}

如果想要修改變量的值,需要在聲明變量時加上mut標記(mutable)表示該變量是可修改的。

fn main() {
  let mut name = "junmajinlong.com";
  println!("{}", name);
  
  name = "gaoxiaofang.com";   // 修改變量
  println!("{}", name);
}

Rust不僅對未被使用過的變量發出警告,還對賦值過但未被使用過的值發出警告。比如變量賦值後,尚未讀取該變量,就重新賦值了。

fn main() {
  let mut name = "junmajinlong.com"; // 警告值未被使用過
  name = "gaoxiaofang.com"; 
  println!("{}", name);
}

Rust是靜態語言,聲明變量時需指定該變量將要保存的值的數據類型,這樣編譯器編譯時才知道為該變量將要保存的數據分配多少內存、允許存放什麼類型的數據以及如何存放數據。但Rust編譯器會根據所保存的值來推導變量的數據類型,推導得到確定的數據類型之後(比如第一次為該變量賦值之後),就不再允許存放其他類型的數據。

fn main() {
  // 根據保存的值推導數據類型
  // 推導結果:變量name為 &str 數據類型
  let mut name = "junmajinlong.com"; 
  //name = 32;  // 再讓name保存i32類型的數據,報錯
}

當Rust無法推導類型時,或者聲明變量時就明確知道該變量要保存聲明類型的數據時,可明確指定該變量的數據類型

fn main() {
  // 指定變量數據類型的語法:在變量名後加": TYPE"
  let age: i32 = 32;  // 明確指定age為i32類型
  println!("{}", name);
  
  // i32類型的變量想存儲u8類型數據,不允許
  // age = 23_u8;
}

雖然Rust是基於表達式的語言,但變量聲明的let代碼是語句而非表達式。這意味著let操作沒有返回值,因此無法使用let來連續賦值

fn main(){
  let a = (let b = 1);  // 錯誤
}

可以使用tuple的方式同時為多個變量賦值,並且可以使用下劃線_佔位表示忽略某個變量的賦值過程


#![allow(unused)]
fn main() {
// x = 11, y = 22, 忽略33
let (x, y, _) = (11, 22, 33);
}

事實上,_佔位符比想象中還更會【偷懶】,其他語言中_表達的含義可能是丟棄其賦值結果(甚至不丟棄),但Rust中的_會直接忽略變量賦值的過程。這導致了這樣一種看似奇怪的現象:使用普通變量名會導致報錯的變量賦值行為,使用_卻不會報錯

例如,下面(1)不會報錯,而(2)會報錯。這裡涉及到了後面所有權轉移的內容,如果看不懂請先跳過,只需記住結論:_會直接忽略賦值的過程。


#![allow(unused)]
fn main() {
// (1)
let s1 = "junmajinlong.com".to_string();
let _ = s1;
println!("{}", s1); // 不會報錯

// (2)
let s2 = "junmajinlong.com".to_string();
let ss = s2;
println!("{}", s2); // 報錯
}

最後要說明的是,Rust中變量賦值操作實際上是Rust中的一種模式匹配,在後面的章節中將更系統、更詳細地介紹Rust模式匹配功能。