Rust 基本語法與變數 – 常數(const)
簡介
在任何程式語言中,**常數(constant)**都是用來保存「不會改變」的資料。Rust 對常數提供了兩種主要形式:const 與 static,其中 const 是最常見且最簡單的方式。掌握 const 的使用方式,能讓程式碼在編譯期就完成檢查,提升執行效能與安全性,對於 初學者 以及 中級開發者 來說都是必備的基礎功。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,最後延伸到實務應用,帶你一步步了解如何在 Rust 中正確、有效地使用 const。
核心概念
1. const 的基本語法
const必須在 編譯期 完全確定其值。- 必須 顯式指定型別,不能依賴型別推斷。
- 宣告時即為 全域(或在區塊作用域內),無法在執行時修改。
// 全域常數,必須加上型別註記
const MAX_CONNECTIONS: u32 = 100;
// 函式內部的區域常數
fn foo() {
const PI: f64 = 3.1415926535;
println!("π ≈ {}", PI);
}
Tip:
const的命名慣例通常使用全大寫、底線分隔,以提升可讀性。
2. 常數與變數的差異
| 項目 | const |
let (變數) |
|---|---|---|
| 可變性 | 不可變,編譯期即固定 | 預設不可變,若加 mut 則可變 |
| 計算時機 | 編譯期 計算 | 執行期 計算 |
| 型別推斷 | 必須明確指定 | 可由編譯器推斷 |
| 記憶體位置 | 直接內嵌於程式碼(常量池) | 位於堆疊或堆上,依值而定 |
3. 常數的類型限制
const 可以是任何 Copy(可複製)類型,包括原始數值、字元、布林、甚至是 陣列、元組。但不允許包含 Drop(需要釋放資源)的類型,例如 String、Vec<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. const 與 static 的差別簡述
| 特性 | 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 類型 |
String、Vec<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)或更安全的同步原語 |
最佳實踐
- 盡量使用
const:只要值在編譯期即可,保持程式碼純粹且效能最佳。 - 命名遵循全大寫:如
MAX_BUFFER_SIZE,讓讀者一眼辨識為常數。 - 將計算搬到
const fn:減少執行期開銷,特別是對於常用的數學或轉換函式。 - 避免在
const中放置大量資料:若資料量超過數 KB,考慮改用static或外部檔案。 - 使用
#[allow(dead_code)]:在測試或範例中若常數未被使用,可避免警告。
實際應用場景
- 配置參數:如 API 端點、版本號、協議常數等,這些在編譯期就已確定。
- 位元旗標:檔案權限、網路協定標誌、硬體寄存器位元設定,都適合用
const定義。 - 編譯期計算:如 CRC 表、哈希表、字元映射表,可透過
const fn產生,避免執行期初始化。 - 泛型常數:在嵌入式開發中,常用
const參數決定緩衝區大小、陣列長度,確保零開銷抽象。 - 測試基礎值:單元測試中常用
const定義測試資料的基礎值,保證測試的可預測性。
總結
const是 編譯期 的不可變值,必須明確指定型別,適合用於配置、旗標、編譯期計算等情境。- 透過
const fn,我們可以把複雜的計算搬到編譯期,讓執行檔更小、效能更佳。 - 常見的錯誤包括忘記型別、使用非
Copy類型或呼叫非const fn,只要遵守 型別顯示、使用Copy、標記const fn三大原則,就能避免大部分問題。 - 在實務開發中,將所有「不會變」的值改寫為
const,不僅提升程式的可讀性,也能讓編譯器在最佳化時發揮更大威力。
掌握了 const 的使用,你就能寫出更安全、高效且易於維護的 Rust 程式碼。祝你在 Rust 的旅程中玩得開心,寫出更好的程式!