本文 AI 產出,尚未審核

Rust 基本語法與變數 – 常數(const

簡介

在任何程式語言中,**常數(constant)**都是用來保存「不會改變」的資料。Rust 對常數提供了兩種主要形式:conststatic,其中 const 是最常見且最簡單的方式。掌握 const 的使用方式,能讓程式碼在編譯期就完成檢查,提升執行效能與安全性,對於 初學者 以及 中級開發者 來說都是必備的基礎功。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,最後延伸到實務應用,帶你一步步了解如何在 Rust 中正確、有效地使用 const


核心概念

1. const 的基本語法

  • const 必須在 編譯期 完全確定其值。
  • 必須 顯式指定型別,不能依賴型別推斷。
  • 宣告時即為 全域(或在區塊作用域內),無法在執行時修改。
// 全域常數,必須加上型別註記
const MAX_CONNECTIONS: u32 = 100;

// 函式內部的區域常數
fn foo() {
    const PI: f64 = 3.1415926535;
    println!("π ≈ {}", PI);
}

Tipconst 的命名慣例通常使用全大寫、底線分隔,以提升可讀性。


2. 常數與變數的差異

項目 const let (變數)
可變性 不可變,編譯期即固定 預設不可變,若加 mut 則可變
計算時機 編譯期 計算 執行期 計算
型別推斷 必須明確指定 可由編譯器推斷
記憶體位置 直接內嵌於程式碼(常量池) 位於堆疊或堆上,依值而定

3. 常數的類型限制

const 可以是任何 Copy(可複製)類型,包括原始數值、字元、布林、甚至是 陣列元組。但不允許包含 Drop(需要釋放資源)的類型,例如 StringVec<T>

// 合法的常數
const FLAG: bool = true;
const COLORS: [u8; 3] = [255, 0, 0]; // RGB 紅色

// 錯誤範例:String 不是 Copy 類型
// const GREETING: String = String::from("Hello"); // 編譯錯誤

4. 常數的運算(常量運算)

Rust 允許在 const 中使用 常量運算(const-evaluated expressions),包括算術、位元運算、函式呼叫(只要該函式是 const fn)。

// 定義一個 const fn
const fn square(x: i32) -> i32 {
    x * x
}

// 使用 const fn 產生常數
const FOUR_SQUARED: i32 = square(4);

5. conststatic 的差別簡述

特性 const static
可變性 永遠不可變 可加 mut 成為可變的全域變數
記憶體位置 內嵌於程式碼 位於全域資料段
初始化時機 編譯期 執行期(程式載入時)
使用場景 常量值、編譯期計算 需要全域唯一實例(如全域緩衝區)

注意:在大多數情況下,若只需要「不會改變的值」,const 已足夠且較安全。


程式碼範例

範例 1:基本常數與列印

// src/main.rs
const APP_NAME: &str = "Rust 常數教學";
const VERSION: u32 = 1;

fn main() {
    println!("程式名稱: {}", APP_NAME);
    println!("版本號: v{}.0", VERSION);
}

說明&str 是字串切片,屬於 Copy,可直接作為 const 使用。


範例 2:使用 const fn 進行編譯期計算

const fn factorial(n: u64) -> u64 {
    let mut result = 1;
    let mut i = 1;
    while i <= n {
        result *= i;
        i += 1;
    }
    result
}

const FACT_5: u64 = factorial(5); // 5! = 120

fn main() {
    println!("5 的階乘是 {}", FACT_5);
}

說明factorial 在編譯期完成計算,FACT_5 的值在執行檔中已被寫死,沒有額外運算成本。


範例 3:陣列常數與索引

const DAYS: [&str; 7] = [
    "星期一", "星期二", "星期三",
    "星期四", "星期五", "星期六", "星期日"
];

fn today(idx: usize) -> &'static str {
    DAYS[idx % 7]
}

fn main() {
    let day = today(3);
    println!("今天是 {}", day); // 輸出: 星期四
}

說明:陣列本身是 Copy,因此可以作為 const&'static str 保證字串在整個程式生命週期內有效。


範例 4:位元旗標(bitflags)

// 定義權限旗標
const READ: u8  = 0b0000_0001;
const WRITE: u8 = 0b0000_0010;
const EXEC: u8  = 0b0000_0100;

const ALL_PERMS: u8 = READ | WRITE | EXEC;

fn has_permission(perms: u8, flag: u8) -> bool {
    perms & flag == flag
}

fn main() {
    let user_perms = READ | EXEC; // 只讀與執行權限
    println!("是否有寫入權限? {}", has_permission(user_perms, WRITE));
    println!("全部權限? {}", has_permission(ALL_PERMS, ALL_PERMS));
}

說明:位元運算在 const 中非常常見,用來定義編譯期的旗標集合。


範例 5:使用 const 於泛型型別參數

struct Buffer<const SIZE: usize> {
    data: [u8; SIZE],
}

impl<const SIZE: usize> Buffer<SIZE> {
    fn new() -> Self {
        Buffer { data: [0; SIZE] }
    }
}

fn main() {
    let buf = Buffer::<16>::new(); // 產生 16 位元組的緩衝區
    println!("緩衝區大小: {}", buf.data.len());
}

說明:Rust 允許在結構體或函式的泛型參數中使用 const,讓編譯期即決定陣列長度等資訊,提高效能與安全性。


常見陷阱與最佳實踐

陷阱 可能的問題 解決方式
忘記指定型別 const 必須明確型別,缺少會導致編譯錯誤 Always 加上 : type
使用非 Copy 類型 StringVec<T> 會產生 cannot be used in a constant 錯誤 改用 &'static str 或陣列,或改用 static
const 中呼叫非 const fn 會被編譯器拒絕 把函式標記為 const fn,或改寫為編譯期可計算的表達式
過度依賴 const 來儲存大型資料 大型陣列會直接嵌入二進位檔,增加檔案大小 考慮使用 static + lazy_static/once_cell 以延遲載入
誤以為 const 可以在執行期改變 會造成邏輯錯誤 記住 const 完全不可變,若需要可變全域,使用 static mut(需 unsafe)或更安全的同步原語

最佳實踐

  1. 盡量使用 const:只要值在編譯期即可,保持程式碼純粹且效能最佳。
  2. 命名遵循全大寫:如 MAX_BUFFER_SIZE,讓讀者一眼辨識為常數。
  3. 將計算搬到 const fn:減少執行期開銷,特別是對於常用的數學或轉換函式。
  4. 避免在 const 中放置大量資料:若資料量超過數 KB,考慮改用 static 或外部檔案。
  5. 使用 #[allow(dead_code)]:在測試或範例中若常數未被使用,可避免警告。

實際應用場景

  1. 配置參數:如 API 端點、版本號、協議常數等,這些在編譯期就已確定。
  2. 位元旗標:檔案權限、網路協定標誌、硬體寄存器位元設定,都適合用 const 定義。
  3. 編譯期計算:如 CRC 表、哈希表、字元映射表,可透過 const fn 產生,避免執行期初始化。
  4. 泛型常數:在嵌入式開發中,常用 const 參數決定緩衝區大小、陣列長度,確保零開銷抽象。
  5. 測試基礎值:單元測試中常用 const 定義測試資料的基礎值,保證測試的可預測性。

總結

  • const編譯期 的不可變值,必須明確指定型別,適合用於配置、旗標、編譯期計算等情境。
  • 透過 const fn,我們可以把複雜的計算搬到編譯期,讓執行檔更小、效能更佳。
  • 常見的錯誤包括忘記型別、使用非 Copy 類型或呼叫非 const fn,只要遵守 型別顯示使用 Copy標記 const fn 三大原則,就能避免大部分問題。
  • 在實務開發中,將所有「不會變」的值改寫為 const,不僅提升程式的可讀性,也能讓編譯器在最佳化時發揮更大威力。

掌握了 const 的使用,你就能寫出更安全高效易於維護的 Rust 程式碼。祝你在 Rust 的旅程中玩得開心,寫出更好的程式!