ch03-02-data-types.md
commit 4284e160715917a768d25265daf2db897c683065
在 Rust 中,每一個(gè)值都屬于某一個(gè) 數(shù)據(jù)類型(data type),這告訴 Rust 它被指定為何種數(shù)據(jù),以便明確數(shù)據(jù)處理方式。我們將看到兩類數(shù)據(jù)類型子集:標(biāo)量(scalar)和復(fù)合(compound)。
記住,Rust 是 靜態(tài)類型(statically typed)語(yǔ)言,也就是說(shuō)在編譯時(shí)就必須知道所有變量的類型。根據(jù)值及其使用方式,編譯器通常可以推斷出我們想要用的類型。當(dāng)多種類型均有可能時(shí),比如第二章的 “比較猜測(cè)的數(shù)字和秘密數(shù)字” 使用 parse
將 String
轉(zhuǎn)換為數(shù)字時(shí),必須增加類型注解,像這樣:
let guess: u32 = "42".parse().expect("Not a number!");
如果不像上面這樣添加類型注解 : u32
,Rust 會(huì)顯示如下錯(cuò)誤,這說(shuō)明編譯器需要我們提供更多信息,來(lái)了解我們想要的類型:
$ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0282]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ consider giving `guess` a type
For more information about this error, try `rustc --explain E0282`.
error: could not compile `no_type_annotations` due to previous error
你會(huì)看到其它數(shù)據(jù)類型的各種類型注解。
標(biāo)量(scalar)類型代表一個(gè)單獨(dú)的值。Rust 有四種基本的標(biāo)量類型:整型、浮點(diǎn)型、布爾類型和字符類型。你可能在其他語(yǔ)言中見(jiàn)過(guò)它們。讓我們深入了解它們?cè)?Rust 中是如何工作的。
整數(shù) 是一個(gè)沒(méi)有小數(shù)部分的數(shù)字。我們?cè)诘诙率褂眠^(guò) u32
整數(shù)類型。該類型聲明表明,它關(guān)聯(lián)的值應(yīng)該是一個(gè)占據(jù) 32 比特位的無(wú)符號(hào)整數(shù)(有符號(hào)整數(shù)類型以 i
開(kāi)頭而不是 u
)。表格 3-1 展示了 Rust 內(nèi)建的整數(shù)類型。我們可以使用其中的任一個(gè)來(lái)聲明一個(gè)整數(shù)值的類型。
表格 3-1: Rust 中的整型
長(zhǎng)度 | 有符號(hào) | 無(wú)符號(hào) |
---|---|---|
8-bit | i8
|
u8
|
16-bit | i16
|
u16
|
32-bit | i32
|
u32
|
64-bit | i64
|
u64
|
128-bit | i128
|
u128
|
arch | isize
|
usize
|
每一個(gè)變體都可以是有符號(hào)或無(wú)符號(hào)的,并有一個(gè)明確的大小。有符號(hào) 和 無(wú)符號(hào) 代表數(shù)字能否為負(fù)值,換句話說(shuō),這個(gè)數(shù)字是否有可能是負(fù)數(shù)(有符號(hào)數(shù)),或者永遠(yuǎn)為正而不需要符號(hào)(無(wú)符號(hào)數(shù))。這有點(diǎn)像在紙上書(shū)寫(xiě)數(shù)字:當(dāng)需要考慮符號(hào)的時(shí)候,數(shù)字以加號(hào)或減號(hào)作為前綴;然而,可以安全地假設(shè)為正數(shù)時(shí),加號(hào)前綴通常省略。有符號(hào)數(shù)以補(bǔ)碼形式(two’s complement representation) 存儲(chǔ)。
每一個(gè)有符號(hào)的變體可以儲(chǔ)存包含從 -(2n - 1) 到 2n - 1 - 1 在內(nèi)的數(shù)字,這里 n 是變體使用的位數(shù)。所以 i8
可以儲(chǔ)存從 -(27) 到 27 - 1 在內(nèi)的數(shù)字,也就是從 -128 到 127。無(wú)符號(hào)的變體可以儲(chǔ)存從 0 到 2n - 1 的數(shù)字,所以 u8
可以儲(chǔ)存從
0 到 28 - 1 的數(shù)字,也就是從 0 到 255。
另外,isize
和 usize
類型依賴運(yùn)行程序的計(jì)算機(jī)架構(gòu):64 位架構(gòu)上它們是 64 位的, 32 位架構(gòu)上它們是 32 位的。
可以使用表格 3-2 中的任何一種形式編寫(xiě)數(shù)字字面值。請(qǐng)注意可以是多種數(shù)字類型的數(shù)字字面值允許使用類型后綴,例如 57u8
來(lái)指定類型,同時(shí)也允許使用 _
做為分隔符以方便讀數(shù),例如1_000
,它的值與你指定的 1000
相同。
表格 3-2: Rust 中的整型字面值
數(shù)字字面值 | 例子 |
---|---|
Decimal (十進(jìn)制) | 98_222
|
Hex (十六進(jìn)制) | 0xff
|
Octal (八進(jìn)制) | 0o77
|
Binary (二進(jìn)制) | 0b1111_0000
|
Byte (單字節(jié)字符)(僅限于u8 ) |
b'A'
|
那么該使用哪種類型的數(shù)字呢?如果拿不定主意,Rust 的默認(rèn)類型通常是個(gè)不錯(cuò)的起點(diǎn),數(shù)字類型默認(rèn)是 i32
。isize
或 usize
主要作為某些集合的索引。
整型溢出比方說(shuō)有一個(gè) ?
u8
?,它可以存放從零到 ?255
?的值。那么當(dāng)你將其修改為 ?256
?時(shí)會(huì)發(fā)生什么呢?這被稱為 “整型溢出”(“integer overflow” ),這會(huì)導(dǎo)致以下兩種行為之一的發(fā)生。當(dāng)在 debug 模式編譯時(shí),Rust 檢查這類問(wèn)題并使程序 panic,這個(gè)術(shù)語(yǔ)被 Rust 用來(lái)表明程序因錯(cuò)誤而退出。第九章 “panic! 與不可恢復(fù)的錯(cuò)誤” 部分會(huì)詳細(xì)介紹 panic。在 release 構(gòu)建中,Rust 不檢測(cè)溢出,相反會(huì)進(jìn)行一種被稱為二進(jìn)制補(bǔ)碼回繞(two’s complement wrapping)的操作。簡(jiǎn)而言之,比此類型能容納最大值還大的值會(huì)回繞到最小值,值 ?
256
?變成 ?0
?,值 ?257
?變成 ?1
?,依此類推。依賴整型回繞被認(rèn)為是一種錯(cuò)誤,即便可能出現(xiàn)這種行為。如果你確實(shí)需要這種行為,標(biāo)準(zhǔn)庫(kù)中有一個(gè)類型顯式提供此功能,?Wrapping
?。 為了顯式地處理溢出的可能性,你可以使用標(biāo)準(zhǔn)庫(kù)在原生數(shù)值類型上提供的以下方法:
- 所有模式下都可以使用 ?
wrapping_*
? 方法進(jìn)行回繞,如 ?wrapping_add
?- 如果 ?
checked_*
? 方法出現(xiàn)溢出,則返回 ?None
?值- 用 ?
overflowing_*
? 方法返回值和一個(gè)布爾值,表示是否出現(xiàn)溢出- 用 ?
saturating_*
? 方法在值的最小值或最大值處進(jìn)行飽和處理
Rust 也有兩個(gè)原生的 浮點(diǎn)數(shù)(floating-point numbers)類型,它們是帶小數(shù)點(diǎn)的數(shù)字。Rust 的浮點(diǎn)數(shù)類型是 ?f32
?和 ?f64
?,分別占 32 位和 64 位。默認(rèn)類型是 ?f64
?,因?yàn)樵诂F(xiàn)代 CPU 中,它與 ?f32
?速度幾乎一樣,不過(guò)精度更高。所有的浮點(diǎn)型都是有符號(hào)的。
這是一個(gè)展示浮點(diǎn)數(shù)的實(shí)例:
文件名: src/main.rs
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
浮點(diǎn)數(shù)采用 IEEE-754 標(biāo)準(zhǔn)表示。?f32
?是單精度浮點(diǎn)數(shù),?f64
?是雙精度浮點(diǎn)數(shù)。
Rust 中的所有數(shù)字類型都支持基本數(shù)學(xué)運(yùn)算:加法、減法、乘法、除法和取余。整數(shù)除法會(huì)向下舍入到最接近的整數(shù)。下面的代碼展示了如何在 ?let
?語(yǔ)句中使用它們:
文件名: src/main.rs
fn main() {
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let floored = 2 / 3; // Results in 0
// remainder
let remainder = 43 % 5;
}
這些語(yǔ)句中的每個(gè)表達(dá)式使用了一個(gè)數(shù)學(xué)運(yùn)算符并計(jì)算出了一個(gè)值,然后綁定給一個(gè)變量。附錄 B 包含 Rust 提供的所有運(yùn)算符的列表。
正如其他大部分編程語(yǔ)言一樣,Rust 中的布爾類型有兩個(gè)可能的值:?true
?和 ?false
?。Rust 中的布爾類型使用 ?bool
?表示。例如:
文件名: src/main.rs
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
使用布爾值的主要場(chǎng)景是條件表達(dá)式,例如 ?if
?表達(dá)式。在 “控制流”(“Control Flow”) 部分將介紹 ?if
?表達(dá)式在 Rust 中如何工作。
Rust的 ?char
?類型是語(yǔ)言中最原生的字母類型。下面是一些聲明 ?char
?值的例子:
文件名: src/main.rs
fn main() {
let c = 'z';
let z: char = '?'; // with explicit type annotation
let heart_eyed_cat = '';
}
注意,我們用單引號(hào)聲明 ?char
?字面量,而與之相反的是,使用雙引號(hào)聲明字符串字面量。Rust 的 ?char
?類型的大小為四個(gè)字節(jié)(four bytes),并代表了一個(gè) Unicode 標(biāo)量值(Unicode Scalar Value),這意味著它可以比 ASCII 表示更多內(nèi)容。在 Rust 中,帶變音符號(hào)的字母(Accented letters),中文、日文、韓文等字符,emoji(繪文字)以及零長(zhǎng)度的空白字符都是有效的 ?char
?值。Unicode
標(biāo)量值包含從 ?U+0000
? 到 ?U+D7FF
? 和 ?U+E000
? 到 ?U+10FFFF
? 在內(nèi)的值。不過(guò),“字符” 并不是一個(gè) Unicode 中的概念,所以人直覺(jué)上的 “字符” 可能與 Rust 中的 ?char
?并不符合。第八章的 “使用字符串存儲(chǔ) UTF-8 編碼的文本” 中將詳細(xì)討論這個(gè)主題。
復(fù)合類型(Compound types)可以將多個(gè)值組合成一個(gè)類型。Rust 有兩個(gè)原生的復(fù)合類型:元組(tuple)和數(shù)組(array)。
元組是一個(gè)將多個(gè)其他類型的值組合進(jìn)一個(gè)復(fù)合類型的主要方式。元組長(zhǎng)度固定:一旦聲明,其長(zhǎng)度不會(huì)增大或縮小。
我們使用包含在圓括號(hào)中的逗號(hào)分隔的值列表來(lái)創(chuàng)建一個(gè)元組。元組中的每一個(gè)位置都有一個(gè)類型,而且這些不同值的類型也不必是相同的。這個(gè)例子中使用了可選的類型注解:
文件名: src/main.rs
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
?tup
?變量綁定到整個(gè)元組上,因?yàn)樵M是一個(gè)單獨(dú)的復(fù)合元素。為了從元組中獲取單個(gè)值,可以使用模式匹配(pattern matching)來(lái)解構(gòu)(destructure)元組值,像這樣:
文件名: src/main.rs
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
}
程序首先創(chuàng)建了一個(gè)元組并綁定到 ?tup
?變量上。接著使用了 ?let
?和一個(gè)模式將 ?tup
?分成了三個(gè)不同的變量,?x
?、?y
?和 ?z
?。這叫做 解構(gòu)(destructuring),因?yàn)樗鼘⒁粋€(gè)元組拆成了三個(gè)部分。最后,程序打印出了 ?y
? 的值,也就是 ?6.4
?。
我們也可以使用點(diǎn)號(hào)(?.
?)后跟值的索引來(lái)直接訪問(wèn)它們。例如:
文件名: src/main.rs
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
這個(gè)程序創(chuàng)建了一個(gè)元組,?x
?,然后使用其各自的索引訪問(wèn)元組中的每個(gè)元素。跟大多數(shù)編程語(yǔ)言一樣,元組的第一個(gè)索引值是 0。
不帶任何值的元組有個(gè)特殊的名稱,叫做 單元(unit) 元組。這種值以及對(duì)應(yīng)的類型都寫(xiě)作 ?()
?,表示空值或空的返回類型。如果表達(dá)式不返回任何其他值,則會(huì)隱式返回單元值。
另一個(gè)包含多個(gè)值的方式是 數(shù)組(array)。與元組不同,數(shù)組中的每個(gè)元素的類型必須相同。Rust 中的數(shù)組與一些其他語(yǔ)言中的數(shù)組不同,Rust中的數(shù)組長(zhǎng)度是固定的。
我們將數(shù)組的值寫(xiě)成在方括號(hào)內(nèi),用逗號(hào)分隔:
文件名: src/main.rs
fn main() {
let a = [1, 2, 3, 4, 5];
}
當(dāng)你想要在棧(stack)而不是在堆(heap)上為數(shù)據(jù)分配空間(第四章將討論棧與堆的更多內(nèi)容),或者是想要確??偸怯泄潭〝?shù)量的元素時(shí),數(shù)組非常有用。但是數(shù)組并不如 vector 類型靈活。vector 類型是標(biāo)準(zhǔn)庫(kù)提供的一個(gè) 允許 增長(zhǎng)和縮小長(zhǎng)度的類似數(shù)組的集合類型。當(dāng)不確定是應(yīng)該使用數(shù)組還是 vector 的時(shí)候,那么很可能應(yīng)該使用 vector。第八章會(huì)詳細(xì)討論 vector。
然而,當(dāng)你確定元素個(gè)數(shù)不會(huì)改變時(shí),數(shù)組會(huì)更有用。例如,當(dāng)你在一個(gè)程序中使用月份名字時(shí),你更應(yīng)趨向于使用數(shù)組而不是 vector,因?yàn)槟愦_定只會(huì)有12個(gè)元素。
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
可以像這樣編寫(xiě)數(shù)組的類型:在方括號(hào)中包含每個(gè)元素的類型,后跟分號(hào),再后跟數(shù)組元素的數(shù)量。
let a: [i32; 5] = [1, 2, 3, 4, 5];
這里,?i32
?是每個(gè)元素的類型。分號(hào)之后,數(shù)字 ?5
?表明該數(shù)組包含五個(gè)元素。
你還可以通過(guò)在方括號(hào)中指定初始值加分號(hào)再加元素個(gè)數(shù)的方式來(lái)創(chuàng)建一個(gè)每個(gè)元素都為相同值的數(shù)組:
let a = [3; 5];
變量名為 ?a
? 的數(shù)組將包含 ?5
? 個(gè)元素,這些元素的值最初都將被設(shè)置為 ?3
?。這種寫(xiě)法與 ?let a = [3, 3, 3, 3, 3];
? 效果相同,但更簡(jiǎn)潔。
數(shù)組是可以在棧(stack)上分配的已知固定大小的單個(gè)內(nèi)存塊??梢允褂盟饕齺?lái)訪問(wèn)數(shù)組的元素,像這樣:
文件名: src/main.fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
在這個(gè)例子中,叫做 ?first
?的變量的值是 ?1
?,因?yàn)樗菙?shù)組索引 ?[0]
? 的值。變量 ?second
?將會(huì)是數(shù)組索引 ?[1]
? 的值 ?2
?。
讓我們看看如果我們?cè)L問(wèn)數(shù)組結(jié)尾之后的元素會(huì)發(fā)生什么呢?比如你執(zhí)行以下代碼,它使用類似于第 2 章中的猜數(shù)字游戲的代碼從用戶那里獲取數(shù)組索引:
文件名: src/main.rs
use std::io;
fn main() {
let a = [1, 2, 3, 4, 5];
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
let element = a[index];
println!("The value of the element at index {index} is: {element}");
}
此代碼編譯成功。如果您使用 ?cargo run
? 運(yùn)行此代碼并輸入 0、1、2、3 或 4,程序?qū)⒃跀?shù)組中的索引處打印出相應(yīng)的值。如果你輸入一個(gè)超過(guò)數(shù)組末端的數(shù)字,如 10,你會(huì)看到這樣的輸出:
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
程序在索引操作中使用一個(gè)無(wú)效的值時(shí)導(dǎo)致 運(yùn)行時(shí) 錯(cuò)誤。程序帶著錯(cuò)誤信息退出,并且沒(méi)有執(zhí)行最后的 ?println!
? 語(yǔ)句。當(dāng)嘗試用索引訪問(wèn)一個(gè)元素時(shí),Rust 會(huì)檢查指定的索引是否小于數(shù)組的長(zhǎng)度。如果索引超出了數(shù)組長(zhǎng)度,Rust 會(huì) panic,這是 Rust 術(shù)語(yǔ),它用于程序因?yàn)殄e(cuò)誤而退出的情況。這種檢查必須在運(yùn)行時(shí)進(jìn)行,特別是在這種情況下,因?yàn)榫幾g器不可能知道用戶在以后運(yùn)行代碼時(shí)將輸入什么值。
這是第一個(gè)在實(shí)戰(zhàn)中遇到的 Rust 安全原則的例子。在很多底層語(yǔ)言中,并沒(méi)有進(jìn)行這類檢查,這樣當(dāng)提供了一個(gè)不正確的索引時(shí),就會(huì)訪問(wèn)無(wú)效的內(nèi)存。通過(guò)立即退出而不是允許內(nèi)存訪問(wèn)并繼續(xù)執(zhí)行,Rust 讓你避開(kāi)此類錯(cuò)誤。第九章會(huì)更詳細(xì)地討論 Rust 的錯(cuò)誤處理機(jī)制,以及如何編寫(xiě)可讀性強(qiáng)而又安全的代碼,使程序既不會(huì) panic 也不會(huì)導(dǎo)致非法內(nèi)存訪問(wèn)。
更多建議: