Easyfun Easy fun Easy to play

Data Types

資料型別

Rust每個值都有其確切的數據類型,分為兩種資料型別子集:純量(scalar)與複合(compound)

Rust是靜態型別語言,代表他必須在編譯時知道所有變數的型別。

Rust編譯器很聰明,可以根據變數的值上下文中的使用方式來自動推導出變數的值,同時他也在某些情況下,無法推導出變數類型。

let guess = “30”.parse().expect(“Not a number!”);

純量型別

純量型別代表單一數值。

Rust有四種主要的純量型別:整數,浮點數,布林及字元。

整數型別

整數是沒有小數點的點數。

Rust中的整數型別

長度 帶號 非帶號
8位元 i8 u8
16位元 i16 u16
32位元 i32 u32
64位元 i64 u64
123位元 i128 u128
系統架構 isize usize

每個變體都可以是帶號或非帶號,並有明確大小。帶號與非帶號的區別是數字能不能有負數,換句話說就是數字能否帶有正負符號,如果沒有的話就只會出現正整數。

每一帶號變體可以儲存數字範圍包含從-(2n - 1) 到 2n - 1 - 1 以內的數字,n就是該變體佔用的位元大小。

所以一個==ib==可以儲存的數字範圍就是從-(27) 到 27 - 1,也就是-128到127。而非帶號可以儲存的數字範圍則是從0到2n - 1,所以==u8==可以儲存的範圍是從0到28 - 1,也就是 0 到 255。

isize與usize型別則是依據你程式運算電腦架構來決定大小,所以上表才用「系統架構」來表示長度:如果你在64位元架構上的話就是64位元;如果是32位元架構的話就是32位元。

Rust中的整數字面值

數字字面值 範例
十進制 98_222
十六進制 0xff
八進制 0o77
二進制 0b1111_0000
位元組(僅限u8) b’A’

你該用到哪些整數型別呢,如果不確定的話,Rust預設的型別是很好的起始點:整數型別預設是i32。而你會用到isize或usize的主要時機是作為某些集合的索引。

整數溢位

假設你有個變數型別是==u8==可以儲存0到255的數值。如果想要改變變數的值超出這個範圍的話,比方256,那麼就會發生整數溢位,這會產生兩種不同的結果。如果是除錯模式編譯的話,Rust會包含整數溢位的檢查,造成程式執行時恐慌(panic)。Rust使用恐慌來表示程式因錯誤而結束。

當你在發佈模式下用–release來編譯,Rust則不會加上整數溢位的檢查而造成恐慌。相反如果發生整數溢位的話,Rust會做出二補數包裝的動作。超出最大值的數值可以被包裝成該型別的最低數值。以==u8==為例的話,256會變成0,257會變成1以此類推。程式不會恐慌,但是該變數可能會得到一個不是你原本預期的數值。通常依靠整數溢位的行為仍然會被視為邏輯錯誤。

要顯示處理可能的溢位的話,你可以使用以下標準函式庫中基本型別提供一系列方法:

⛵ 將所有操作用==wrapping_==方法包裝,像是==wrapping_add==。 ⛵ 使用==checked_==方法,如果有溢位的話其會回傳==None==數值。 ⛵ 使用==overflowing_==方法,其會回傳數值與一個布林值來顯示是否有溢位發生。 ⛵ 屬於==saturating_==,讓數值溢位時保持在最小或最大值。

浮點數型別

Rust還有針對小數點的浮點數提供兩種基本型別:f32和f64,分別佔有32位元與64位元的大小。而預設的型別為f64,現代處理速度幾乎和f32一樣卻還能擁有更高的精準度。所有的浮點數型別都是帶號的(signed)。

fn main() {
  let x = 2.0;  // f64
  let y: f32 = 3.0; // f32
}

浮點數是依照IEEE-754所定義的,f32型別是單精度浮點數,而f64是倍精度浮點數。

數值運算

Rust支援你所想到的數值型別基本運算:加法,減法,除法和取餘。整數除法會取最接近零的下界數值。

fn main() {
  // 加法
  let sum = 5 + 10;

  // 減法
  let difference = 95.5 - 4.3;

  // 乘法
  let product = 4 * 30;

  // 除法
  let quotient = 56.7 / 32.2;
  let truncated = -5 / 3;  // 結果為-1

  // 取餘
  let remainder = 43 % 5;
}

每一個陳述式中的表達式都使用了一個數學運算符號並計算出一個數值出來,賦值給該變數。

布林型別

Rust中的布林型別有兩個值:true和false。布林值大小為一個位元組。要在Rust中定義布林型別的話:

fn main() {
  let t = true;

  let f:bool - false; // 型別詮釋的方式
}

布林值最常使用的方式之一是作為條件判斷,像是在if表達式中使用。

字元型別

Rust的char型別是最基本的字母型別。

fn main(){
  let c = 'z';
  let z: char = 'Z'; // 明確標註型別的寫法
  let heart_eyed_cat = '😺';
}

注意到char字面值是用單引號賦值,宣告字串字面值時才是用雙引號。Rust的char型別大小為四個位元組並表示為一個Unicode純量數值,代表他能擁有的字元比ASCII還來得多。標音字母(Accented letters),中文,日文,韓文,表情符號以及零長度空格都是Rust==char==的有效字元。

Unicode純量數值的範圍包含從==U+0000==到==U+E000==以及==U+E000==到==U+10FFFF==。但一個「字元」並不是真正的Unicode概念,對於什麼是一個「字元」的看法可能會和Rust的char不一樣。

複合型別

複合型別可以組合數個數值為一個型別,Rust有兩個基本複合型別:元組(tuples)和陣列(arrays)。

元組(Tuple)型別

元組是個將許多不同型別的數值合成一個復合型別的方法。元組擁有固定長度:一旦宣告好後,他們就無法增長或縮減。

建立一個元組的方法是寫一個用括號囊括起來的數值列表,每個值再用逗號分隔開來。元組的每一格都是一個獨立型別,不同數值不必事相同型別。

fn main() {
  let tup = (500, 6.4, 1);

  let (x, y, z) = tup;

  println!("y的數值為: {y}");
}

此式是建立了一個元組然後賦值給tup,接著用模式配對和let將tup拆成三個個別的變數x,y,z。這就是解構(destructuring),因它將單一元組拆成了三個部分。最後將y的值印出來,也就是6.4。

我們也可用句號(.)再加上數值的索引來取得元組內的元素。

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;
}

此建立了元組x,然後用他們個別的索引來存取元組的元素。和多數語言一樣,元組的第一個索引是0。

沒有任何數值的元組有一種特殊名稱叫做單元型別(Unit),其數值與型別都寫作(),代表一個空的數值或空的回傳型別。表達式要是沒有回傳任何數值的話,他們就會隱式回傳單位型別。

陣列型別

另一方法取得數值集合是使用陣列。和元組不同的是,陣列中每個型別必須是一樣的。跟其他寫法不同,Rust的陣列是固定長度。

我們將數值寫在陣列中的括號內,每個數值再用逗號區隔開:

fn main() {
  let a = [1, 2, 3, 4, 5];
}

當你想要資料被分配在堆疊(stack)而不是堆積(heap),使用陣列是好選擇。當你想確定妳永遠會取得固定長度的元素也是。所以陣列不像向量(vector)型別那麼有彈性,向量是標準函式庫提供的集合型別,類似陣列但允許變更長度大小。

當你知道元素多寡不會變的話,陣列是個好選擇。例如使用月份的話,就能用陣列宣告:

let months = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"];

要詮釋陣列型別的話,可以在中括號寫出型別和元素個數,並用分號區隔開來:

let a: [i32; 5] = [1, 2, 3, 4, 5];

i32在此是每個元素的型別,在分號後面的數字5指的是有5個元素。

如果想建立的陣列中每個元素數值都一樣的話,可以指定一個數值後加上分號,最後寫出元素個體。

let a = [3; 5];

陣列a會包含5個元素,然後每個元素的初始化數值均為3。寫法與==let a = [3, 3, 3, 3, 3];==一樣但較精簡。

獲取陣列元素

一個陣列是被分配在堆疊上且知固定大小的一整塊記憶體,可以用索引來取得陣列的元素:

fn main() {
  let a = [1, 2, 3, 4, 5];

  let first = a[0];
  let second = a[1];
}

變數first會得到數值1,這是陣列索引[0]的數值。變數second則會從陣列索引[1]得到數值2。

無效的陣列元素存取

use std::io;

fn main() {
  let a = [1, 2, 3, 4, 5];

  println!("請輸入陣列索引");

  let mut index = String::new();

  io::stdin()
      .read_line(&mut index)
      .expect("讀行失敗");

  let index: usize = index
      .trim().parse().exprct("輸入的索引並非數字");

  let element = a[index];

  println!("索引 {index} 元素的數值為: {element}");
}

此透過 !==cargo run==! 執行並輸入0,1,2,3,4,程式將會印出陣列索引對應值。但輸入超過陣列長度,像是10的話,會看到此結果

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

此會在使用無效數值進行索引操作時產生執行時(runtime)錯誤。會退出並回傳錯誤訊息,且不會執行最後的println!。當嘗試使用索引存取元素時,Rust會檢查你的索引是否小於陣列長度,如果索引大於或等於陣列長度的話,Rust會恐慌。

這是Rust記憶體安全原則給的保障,當你提供不正確的索引時,無效的記憶體可能會被存取。Rust會保護你免於這樣的錯誤,並立刻離開,而不是允許記憶體存取並繼續。