Rust 基本型別有6種,又可以再分成2種原生資料型別子集:純量(scalar)與複合(compound)。
純量:整數(integer)、浮點數(floating-point)、布林(boolean)以及字元(character),只會有一個數值。
複合:元組(tuples)和陣列(arrays),數個數值的集合。
和 JavaScript 和 Python 比起來會發現基本型別裡沒有空值(null 或 None),這和 Rust 的核心理念:內存安全和並發安全有關。
先看整數的部分,Rust 內建的整數型別如下。
長度 | 帶號 | 非帶號 |
---|---|---|
8 位元 | i8 |
u8 |
16 位元 | i16 |
u16 |
32 位元 | i32 |
u32 |
64 位元 | i64 |
u64 |
128 位元 | i128 |
u128 |
系統架構 | isize |
usize |
簡單看可以分成兩部分,i
和u
表示是否帶正負符號,後面則是表示有多少位元(bit)可以用來儲存。
帶正負的 i
(signed)開頭系列可以存的數字從-(2ⁿ⁻¹) ~ 2ⁿ⁻¹-1
, u
(unsigned)就是 0 ~ 2ⁿ-1
以 i8
舉例,代表有 8 個位元儲存帶正負符號的數字,紀錄範圍就是 -2⁷ ~ 2⁷-1
也就是 -128~127, u8
的話則是 0~255。
isize
與 usize
型別則是依據你程式運行的電腦架構來決定大小,例如 64 位元架構上的話就是 64 位元,會用到的情況包括:
之後如果有遇到這些情境再來看。
回到整數型別,標示上有一些小訣竅。
我們直接寫在程式碼裡面的數字稱為數字字面值(number literals)。
57u8
來指定型別。_
分隔方便閱讀,比如說1_000
其實就和指定1000
的數值一樣。其他標示的方式如官網所示:
數字字面值 | 範例 |
---|---|
十進制 | 98_222 |
十六進制 | 0xff |
八進制 | 0o77 |
二進制 | 0b1111_0000 |
位元組(僅限u8 ) |
b'A' |
和 JavaScript 比起來更細的整數型別可以有效地利用空間,不過可以選更小的數字範圍就代表數字更容易超過這個範圍了,那超過的話會發生什麼事呢?
先看一開始賦值就超過範圍的話:
fn main() {
println!("Hello");
let num = 250i8;
println!("{}", num);
}
$ cargo run
Compiling types_int v0.1.0 (/Users/lanshihchun/Desktop/rust/types_int)
error: literal out of range for `i8`
--> src/main.rs:3:15
|
3 | let num = 250i8;
| ^^^^^
|
= note: the literal `250i8` does not fit into the type `i8` whose range is `-128..=127`
= help: consider using the type `u8` instead
= note: `#[deny(overflowing_literals)]` on by default
error: could not compile `types_int` (bin "types_int") due to 1 previous error
可以看到在編譯過程就會報錯,而且會有個滿明確的錯誤訊息。
那如果是經過操作才超過呢?
fn main() {
println!("Hello");
let mut num = 250u8;
num += 20;
println!("{}", num);
}
$ cargo run
Compiling types_int v0.1.0 (/Users/lanshihchun/Desktop/rust/types_int)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/types_int`
Hello
thread 'main' panicked at src/main.rs:4:5:
attempt to add with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
可以看到有編譯成功,在 run time 的時候才出錯,這是因為我們目前編譯是用 debug 模式,如果改成 release 模式輸出的話結果會不同,在cargo run
多加上 --release
再試一次:
$ cargo run --release
Compiling types_int v0.1.0 (/Users/lanshihchun/Desktop/rust/types_int)
Finished `release` profile [optimized] target(s) in 0.42s
Running `target/release/types_int`
Hello
14
可以看到除了編譯成功以外,run time 也執行完成並且沒有任何錯誤訊息,最重要的是,結果很明顯和我們想要的不一樣,這就是所謂的 整數溢位(Integer Overflow)
,簡單的說就是會超過的部分會從頭繼續往下走,以上面的例子來說 u8
最多可以存到 255,我們預期的結果是 250 + 20 = 270 ,超過的部分回到 0 開始,所以 256 的結果變成 0,270 的結果就變成 14 了。
必須要非常小心兩種模式的行為不同,即使測試環境會報錯,一旦到正式環境如果沒適當處理就很難被發現(不會報錯),所以在定義型別的時候一定要謹慎選擇合適的大小(Rust 預設為 i32
),否則最慘的情況會是造成髒資料又沒辦法在第一時間發現及修正,如果遇到像算金額之類,難以想像會造成多大的損失。
也因此 Rust 提供了多種方法來顯式處理可能的溢位,包括 wrapping_*
、checked_*
、overflowing_*
和 saturating_*
方法,可以根據需求來處理溢位情況。
以下是各種方法的範例:
wrapping_*
方法 (default)wrapping_*
方法會在發生溢位時繞回(wrap around),例如加法運算的結果超過類型的最大值時,會回到類型的最小值。沒指定的話就是 Rust 的預設行為。
fn main() {
let a: u8 = 255;
let b: u8 = 1;
let result = a.wrapping_add(b);
println!("Wrapping add: {} + {} = {}", a, b, result);
}
$ cargo run --release
Wrapping add: 255 + 1 = 0
checked_*
方法checked_*
方法在發生溢位時會返回 None
,否則返回 Some(結果)
。
fn main() {
let a: u8 = 255;
let b: u8 = 1;
match a.checked_add(b) {
Some(result) => println!("Checked add: {} + {} = {}", a, b, result),
None => println!("Checked add: Overflow occurred"),
}
}
$ cargo run --release
Checked add: Overflow occurred
overflowing_*
方法overflowing_*
方法返回一個元組 (結果, bool)
,其中 bool
表示是否發生溢位。
fn main() {
let a: u8 = 255;
let b: u8 = 1;
let (result, overflow) = a.overflowing_add(b);
println!("Overflowing add: {} + {} = {}, Overflow: {}", a, b, result, overflow);
}
$ cargo run --release
Overflowing add: 255 + 1 = 0, Overflow: true
saturating_*
方法saturating_*
方法在溢位時會返回類型的最大值或最小值。
超過最大值的情況:
fn main() {
let a: u8 = 255;
let b: u8 = 1;
let result = a.saturating_add(b);
println!("Saturating add: {} + {} = {}", a, b, result);
}
$ cargo run --release
Saturating add: 255 + 1 = 255
小於最小值的情況:
fn main() {
let a: u8 = 255;
let b: u8 = 1;
let result = b.saturating_sub(a);
println!("Saturating sub: {} - {} = {}", b, a, result);
}
$ cargo run --release
Saturating sub: 1 - 255 = 0
Rust 在整數型別的處理上比我之前碰過的程式語言有更精細的控制,透過不同位元長度和符號的選擇,可以更靈活地運用內存空間,可以理解為何 Rust 在開發性能要求高的應用會更具優勢。
另外內建了多種方式來處理溢位情況,看得出 Rust 對安全性的重視。