之前 echo function 有提到 Rust 預設變數(variable)是不可變的。Rust 設計上偏好讓開發者知道自己在做什麼,有點像在引導開發者思考方向,變數和 mut
的設計就是這樣。所有需要可變的地方都要用 mut
顯示的表達,會很自然而然地思考哪些變數是真的需要被變動的,這樣的設計對安全性、效能、可維護性都有正向作用。
安全性:
如果寫一個多執行緒程式,一個變數可能會被多個執行緒讀取,Rust 會在編譯期就做一些檢查,保證不會有操作去更改到不可變變數的值,這樣就不需要在程式執行後擔心某個執行緒會意外地修改它的值,導致其他執行緒在讀取該變數時出現非預期的結果,尤其這種錯誤很難排查。
效能:
當變數不可變時,編譯器可以以該值不會改變為前提進行優化,因此它可以減少對這個變數的重複讀取或重新計算,從而提高程式的性能。此外,Rust 也可以更安全地在多執行緒環境下共享不可變數據,而無需加鎖,進一步提高了並行程式的效率。
可維護性:mut
的顯式表達使程式碼更好理解,當一個變數在其生命週期內不會改變時,開發者能更輕鬆地判斷程式的行為,而使用 mut
的地方代表需要修改某個變數,幫助開發者在審查程式碼時可以更明確的找出可能有的副作用或是影響範圍。
接著我們來看程式碼:
fn main() {
let x = 5;
println!("x 的數值為:{x}");
x = 6;
println!("x 的數值為:{x}");
}
$ cargo run
--> src/main.rs:4:5
|
2 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
3 | println!("x 的數值為:{x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
當我們嘗試重複賦值到不可變的變數時,Rust 的編譯器就會告訴我們這是不被允許的,如果需要重複賦值就需要用 mut
明確標示這個變數是可變的。
fn main() {
let mut x = 5;
println!("x 的數值為:{x}");
x = 6;
println!("x 的數值為:{x}");
}
接著在前面的元組與陣列有提到它們的長度都是不可變的,那元素是可以被改變的嗎?
答案是和前面純量的變數一樣,默認是不可變的。
fn main() {
let my_tuple = (0, 'a', false);
println!("original: {:?}", my_tuple);
my_tuple.1 = 'b';
println!("updated: {:?}", my_tuple);
}
$ cargo run
error[E0594]: cannot assign to `my_tuple.1`, as `my_tuple` is not declared as mutable
--> src/main.rs:4:5
|
4 | my_tuple.1 = 'b';
| ^^^^^^^^^^^^^^^^ cannot assign
|
help: consider changing this to be mutable
|
2 | let mut my_tuple = (0, 'a', false);
| +++
For more information about this error, try `rustc --explain E0594`.
編譯器提示也很清楚了,一樣在宣告變數的地方加上 mut
就可以了。
fn main() {
let mut my_tuple = (0, 'a', false);
println!("original: {:?}", my_tuple);
my_tuple.1 = 'b';
my_tuple.2 = true;
println!("updated: {:?}", my_tuple);
}
original: (0, 'a', false)
updated: (0, 'b', true)
不過需要注意的是,因為是以變數為單位宣告,就代表這個元組所有的元素都是可變了,陣列的邏輯也是一樣的,它們都不允許有的元素可變有的元素不可變的情況。
另外程式裡面常用到的另一個概念是常數(constants)。
常數和不可變變數一樣,數值與名稱綁定且不允許被改變,但是常數又再更嚴格。
mut
,代表常數是永遠不可變的。error: const globals cannot be mutable
fn main() {
const START_POINT = 0;
println!("start point: {START_POINT}");
}
error: missing type for `const` item
--> src/main.rs:10:22
|
10 | const START_POINT = 0;
| ^ help: provide a type for the constant: `: i32`
use rand::Rng;
fn main() {
let random_num = rand::thread_rng().gen_range(1..=100);
const START_POINT: i32 = 0 + random_num;
println!("start point: {START_POINT}");
}
error[E0435]: attempt to use a non-constant value in a constant
--> src/main.rs:5:34
|
5 | const START_POINT: i32 = 0 + random_num;
| ----------------- ^^^^^^^^^^ non-constant value
| |
| help: consider using `let` instead of `const`: `let START_POINT`
另外 Rust 習慣常數的命名用英文大寫,英文詞彙中間用 _
隔開,例如:
fn main() {
const START_POINT: i32 = 0;
println!("start point: {START_POINT}");
}
雖然用其他命名方式也不影響程式執行,例如把 START_POINT
改成 startPoint
,但編譯器會警告,建議還是照它的命名習慣。
$ cargo run
warning: constant `startPoint` should have an upper case name
--> src/main.rs:2:11
|
2 | const startPoint: i32 = 0;
| ^^^^^^^^^^ help: convert the identifier to upper case: `START_POINT`
|
= note: `#[warn(non_upper_case_globals)]` on by default
warning: `variables` (bin "variables") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
Running `target/debug/variables`
start point: 0
Rust 支援全局變數(global variable),在 Rust 中稱為靜態變數(static variable)。使用關鍵字 static
宣告。靜態變數有以下特點:
'static
生命週期,意味著它們在整個程式生命週期中都有效。lazy_static
巨集。mut
)。不可變靜態變數的例子:
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("name 為:{}", HELLO_WORLD);
}
可變靜態變數的情況:
unsafe
關鍵字。static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
unsafe
關鍵字會開啟一個新的程式碼區塊,封裝不安全的操作。
這種設計允許開發者在必要時繞過 Rust 的一些安全檢查,但同時也將安全性的責任轉移到開發者身上。
訪問或修改可變靜態變數就是需要使用unsafe
的操作之一。
對於簡單的全局值,優先使用const
,常數更安全、更容易優化。
如果要使用可變全域變數,謹慎使用全局可變狀態,確保使用適當的同步機制,可以利用 Rust 中內建並發安全型別:Atomic
(原子操作)、Mutex
(互斥鎖)和 RwLock
(讀寫鎖)等等。