本文 AI 產出,尚未審核

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

簡介

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

本篇文章將從 基本語法可變性遮蔽(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 必須在宣告時即給定值,且不可使用 mutstatic 則可以是可變的,但必須透過 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:結合 constmut

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)。 大多數情況下避免全域可變,改用 MutexRwLock 包裝的 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_strpush 等方法,常見於建立動態訊息或 JSON 序列化。

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

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


總結

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

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