本文 AI 產出,尚未審核

Rust 基本語法與變數 ── 變數宣告(let、可變性 mut)

簡介

在任何程式語言的學習曲線上,變數的宣告與使用往往是最先接觸的概念。Rust 以安全、零成本抽象聞名,而變數的不可變性(immutability)正是它防止資料競爭、提升執行效率的核心機制。掌握 let 與 mut 的語法與行為,不僅能寫出符合所有權規則的程式,也能在實務開發中避免常見的錯誤與效能陷阱。

本篇文章將從 基本語法、可變性、遮蔽(shadowing) 等概念逐一說明,並提供多個實用範例,讓你在寫 Rust 程式時能夠自信地管理資料的生命週期與可變性。


核心概念

1. let:宣告不可變變數

在 Rust 中,預設的變數是 不可變(immutable)的。這意味著一旦賦值後,變數的內容就不能再被改變,編譯器會在編譯階段直接拒絕嘗試修改的程式碼。

fn main() {
    // 宣告不可變變數 x,型別會被編譯器自動推斷為 i32
    let x = 10;
    println!("x = {}", x);

    // x = 20; // <- 編譯錯誤:cannot assign twice to immutable variable `x`
}

為什麼預設不可變?

  • 防止意外修改資料,提升程式的可預測性。
  • 讓編譯器在最佳化時可以假設變數不會變動,產生更快的機器碼。

2. mut:讓變數可變

若真的需要在程式執行過程中改變變數的值,必須在宣告時加上 mut 關鍵字,明確告訴編譯器「這個變數是可變的」。

fn main() {
    let mut counter = 0; // 可變變數
    for _ in 0..5 {
        counter += 1; // 合法的賦值
    }
    println!("counter = {}", counter); // 輸出 5
}

注意:mut 只影響變數本身的綁定(binding),而不會改變其所指向資料的所有權或借用規則。

3. 變數遮蔽(Shadowing)

Rust 允許在相同作用域內使用相同的變數名稱重新宣告,稱為 遮蔽。遮蔽不同於 mut,因為它會產生一個全新的變數,舊的變數則被「隱藏」且在此之後不再可見。

fn main() {
    let x = 5;          // 第一次宣告,x 為 i32
    let x = x + 1;      // 產生新變數 x,值為 6
    let x = x * 2;      // 再產生新變數 x,值為 12
    println!("最終的 x = {}", x); // 輸出 12
}

遮蔽常用於類型轉換或暫時改變變數的可變性:

fn main() {
    let spaces = "   ";               // &str
    let spaces = spaces.len();        // 重新宣告為 usize
    println!("字串長度 = {}", spaces);
}

4. 型別註記與推斷

雖然 Rust 具備強大的型別推斷能力,但在某些情況下(例如需要明確的浮點型別或大整數),仍建議使用型別註記。

fn main() {
    // 明確指定為 f64
    let pi: f64 = 3.1415926535;
    // 推斷為 i32
    let answer = 42;
    println!("pi = {}, answer = {}", pi, answer);
}

5. 常量(const)與靜態變數(static)

常量 與 靜態變數 與 let 的差別在於它們在編譯期就已確定值,且全域有效。const 必須在宣告時即給定值,且不可使用 mut;static 則可以是可變的,但必須透過 unsafe 存取。

const MAX_POINTS: u32 = 100_000;
static GREETING: &str = "Hello, Rust!";

程式碼範例

範例 1:基本不可變與可變宣告

fn main() {
    // 不可變變數
    let name = "Alice";
    println!("Hello, {}", name);

    // 可變變數
    let mut score = 0;
    score += 10;
    println!("Score = {}", score);
}

重點:name 只能讀取,若嘗試 name = "Bob" 會編譯失敗;score 因加上 mut,才能在後續程式碼中改變。


範例 2:遮蔽與型別轉換

fn main() {
    let value = "42";          // &str
    let value = value.parse::<i32>().unwrap(); // 轉成 i32
    let value = value as f64 * 1.5; // 再轉成 f64 並做運算
    println!("最終結果 = {}", value); // 63.0
}

說明:每一次 let 都產生新變數,舊的 value 被隱藏,讓程式碼在同一作用域內完成多步轉換而不需要額外的變數名稱。


範例 3:可變性與所有權的互動

fn main() {
    let mut s = String::from("Rust");
    // s 仍然擁有所有權,我們可以修改它的內容
    s.push_str(" Programming");
    println!("{}", s); // Rust Programming
}

關鍵:String 是堆積(heap)資料,只有在變數本身是 mut 時,才能呼叫會改變內部緩衝區的 API(如 push_str)。


範例 4:在迴圈中使用可變變數

fn main() {
    let mut total = 0;
    for i in 1..=5 {
        total += i; // 每次迭代都累加
    }
    println!("1+2+3+4+5 = {}", total); // 15
}

技巧:若只需要一次性的累加,亦可使用 Iterator::sum(),但手動累加有助於理解 mut 的實際作用。


範例 5:結合 const 與 mut

const MAX: i32 = 10;

fn main() {
    let mut counter = 0;
    while counter < MAX {
        println!("counter = {}", counter);
        counter += 1;
    }
}

說明:MAX 為編譯期常量,無法被改變;counter 為可變變數,用於控制迴圈。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記加 mut 嘗試修改變數卻忘記在宣告時加 mut,會得到「cannot assign to immutable variable」的編譯錯誤。 在需要改變值的地方,先檢查變數是否真的需要是可變的,若是則加 mut。
誤用遮蔽 使用遮蔽時不小心把舊變數隱藏,導致後續程式碼仍在使用舊值,產生邏輯錯誤。 在遮蔽前加上註解說明意圖,或使用不同的變數名稱以提升可讀性。
可變參考與可變變數混淆 同時持有可變參考與可變變數會觸發借用檢查錯誤。 了解所有權與借用規則,盡量在同一作用域內只保留一個可變借用。
在 const 中使用 mut const 只能是不可變的,若需要全域可變狀態應使用 static mut(需 unsafe)。 大多數情況下避免全域可變,改用 Mutex、RwLock 包裝的 static。
過度使用 mut 把所有變數都標記為 mut,失去 Rust 的安全保證。 僅在確實需要改變值的情況下使用 mut,其餘保持不可變。

最佳實踐

  1. 預設不可變:先寫 let,只有在真的需要改變時才改為 let mut。
  2. 使用遮蔽做型別轉換:不必額外產生新變數名稱,保持程式碼簡潔。
  3. 盡量在最小作用域內宣告變數:減少變數生命週期,降低借用衝突的機會。
  4. 加入註解說明 mut 的必要性:讓團隊成員快速了解為何此變數可變。

實際應用場景

  1. 計算器或統計分析
    需要在迴圈中累加或更新統計值時,let mut total = 0; 是最直觀的寫法。

  2. 狀態機(State Machine)
    透過可變變數保存當前狀態,並在每次事件觸發時更新,例如 let mut state = State::Idle;。

  3. 字串拼接與日誌
    String 必須是可變的才能使用 push_str、push 等方法,常見於建立動態訊息或 JSON 序列化。

  4. 配置參數的預處理
    先以 let 讀取環境變數,然後利用遮蔽把字串轉成適當的型別(usize、bool),最後再以不可變變數傳遞給其他模組。

  5. 嵌入式系統的暫存緩衝
    在資源受限的環境下,使用 let mut buffer = [0u8; 128]; 來作為暫存區,隨時寫入感測器資料。


總結

  • let 為 Rust 中宣告變數的關鍵字,預設不可變,提升程式安全與編譯器最佳化空間。
  • mut 必須明確寫在變數宣告前,才能允許在之後的程式碼中改變其值。
  • 遮蔽(shadowing) 提供了在同一作用域內重新宣告同名變數的能力,常用於型別轉換或暫時改變可變性。
  • 正確使用 const、static、mut,配合所有權與借用規則,可寫出既安全又高效的 Rust 程式。

掌握變數宣告的細節,是踏入 Rust 世界的第一步。未來在處理更複雜的資料結構、併發程式或嵌入式開發時,這些基礎概念將持續為你提供堅實的基礎。祝你寫程式愉快,玩得開心!