# 資訊 主要紀錄來自官方文檔,預計都是從這篇透徹學習 Rust,然後在這裡做點屬於自己的筆記。 1. 教材:[Rust 程式設計語言](https://rust-lang.tw/book-tw/ch01-03-hello-cargo.html) 2. 上次閱讀到:[切片型別](https://rust-lang.tw/book-tw/ch04-03-slices.html) ## Clean code 標準 - Rust 程式碼使用 _snake case_ 式作為函式與變數名稱的慣例風格。所有的字母都是小寫,並用底線區隔單字。 - 由於 Rust 是門基於表達式(expression-based)的語言。 ## 特色 - Rust 是一門**靜態型別**語言。 - **Rustaceans** 是稱呼 Rust 使用者的暱稱。 ### 所有權(Owenship) 所有權讓 Rust 不需要垃圾回收(garbage collector)就可以保障記憶體安全,其中所有權衍生出的特色有借用、切片與 Rust 如何在記憶體配置資料。 - Rust 中每個數值都有個**擁有者(owner)**。 - 同時間只能有一個擁有者。 - 當擁有者離開作用域時,數值就會被丟棄。 - 會將淺拷貝與深拷貝稱作**移動(move)**,因為指標複製的時候會無效第一個變數。 - 想要深拷貝沒有 `Copy` 特徵、記錄在堆疊的資料需要呼叫 `clone` 函數。 ### 參考與借用 ```rust let s = String::new("Hello, world"); let a = &s; ``` - 同時能有多個**不可變參考**。 - 同時只能有一個**可變參考**。 - 同時只能有一種參考,可變或不可變。 - 參考必須永遠有效。 ### 切片型別 ## 基本函式以及打印函式 ```rust fn main() { println!("Hello, world!"); } ``` # Cargo - `cargo new` 產生專案。 - `cargo build` 建構專案。 - `cargo run` 同時建構並執行專案。 - `cargo check` 建構專案來檢查錯誤,但不會產生執行檔。 Cargo 會儲存建構結果在 `target/debug` 目錄底下,而不是放在與我們程式碼相同的目錄。 ### 建構(Build) - 使用 `cargo build` 以 debug profile 在 `target/debug` 中產生執行檔。 - 使用 `cargo build --release` 以 release profile 在 `target/release` 中產生執行檔。 # 範例程式 ## Hello world 在 Rust 裡面所有的東西感覺都很像 Java 跟 C 混合,像是我們要定義主函式 `main()` 來執行所有的程式。 1. 使用 `fn` 預留字代表函式。 2. 使用 `println!()` 印出東西(跟 Java 很像)。 ```rust fn main() { println!("Hello world!"); } ``` ## 猜謎遊戲 這裡要注意在 Rust 中引用套件的語法為 `use <package>::<module>;`。 我把一些語言的套件引用方法寫在這邊: 1. **C/C++** `#include <package>` 2. **Java** `import <package>.<module>;` 3. **Python** `from <package> import <module>` 4. **Rust** `use <package>::<module>;` 首先我們加上  `use`  這行:`use rand::Rng;`。`Rng`  特徵(trait)定義了隨機數字產生器實作的方法,所以此特徵必須引入作用域,我們才能使用這些方法。第十章會詳細解釋特徵。 - 我們在第一行呼叫的  `rand::thread_rng`  函式會回傳我們要使用的特定隨機數字產生器:這會位於目前執行緒(thread)並由作業系統提供種子(seed)。 - 然後我們對隨機數字產生器呼叫  `gen_range`  方法。此方法由  `Rng`  特徵所定義,而我們則是用  `use rand::Rng;`  陳述式將此特徵引入作用域中。 - `gen_range`  方法接收一個範圍表達式作為引數並產生一個在此範圍之間的隨機數字。我們所使用的範圍表達式的格式爲  `start..=end`。這個範圍會包含下限和上限,所以我們需要指定  `1..=100`  來索取 1 到 100 之間的數字。 $$[\text{start}\text{..}\text{end}]$$ ```toml [dependencies] rand = "0.8.5" ``` ```rust! use rand::Rng; use std::cmp::Ordering; use std::io; fn main() { println!("請猜測一個數字!"); let secret_number: u32 = rand::thread_rng().gen_range(1..=100); loop { println!("請輸入你的猜測數字。"); let mut guess: String = String::new(); io::stdin() .read_line(&mut guess) .expect("讀取該行失敗"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("你的猜測數字:{guess}"); match guess.cmp(&secret_number) { Ordering::Less => println!("太小了!"), Ordering::Greater => println!("太大了!"), Ordering::Equal => { println!("獲勝!"); break; }, } } } ``` ## Swap - 記得 `mut` 的規劃跟 pass by reference。 - 這裡使用動態陣列,語法是將原本的陣列加上 `vec!` 這個 macro。 ```rust! fn swap(arr: &mut Vec<i32>, front: usize, rear: usize) { let temp: i32 = arr[front]; arr[front] = arr[rear]; arr[rear] = temp; } fn main() { let mut arr: Vec<i32> = vec![1, 3, 2]; println!("Before swaping: {:?}", arr); swap(&mut arr, 1, 2); println!("After swaping: {:?}", arr); } ``` ```shell! $ cargo run Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s Running `target/debug/learn` Before swaping: [1, 3, 2] After swaping: [1, 2, 3] ``` ## 參考 ### 不可變參考 ```rust fn refence(num: &String) { println!("{num}"); } fn main() { let num = String::from("hello"); refence(&num); println!("{num}"); } ``` ### 可變參考 ```rust fn borrow(msg: &mut String) { msg.push_str(", world"); } fn main() { let mut msg = String::from("hello"); borrow(&mut msg); println!("{msg}"); } ``` 1. 在 `let msg` 定義的時候要加上 `mut`:`let mut msg`。 2. 在參數定義的時候要加上可變與參考關鍵字:`&mut`。 3. 在引數傳遞的時候要加上可變與參考關鍵字: `(&mut msg)`。 ## Insertion Sort ```rust fn main() { let mut arr: Vec<i32> = vec![5, 4, 3, 2, 1]; insertion_sort(&mut arr); println!("{:?}", arr); } fn insertion_sort(arr: &mut Vec<i32>) { let len = arr.len(); for j in 1..len { let key = arr[j]; let mut i = j; while i > 0 && arr[i - 1] > key { arr[i] = arr[i - 1]; i -= 1; } arr[i] = key; } } ``` - 你可以用 `Vec<type>.len()` 去拿大小。 - `for` 迴圈只能這樣用,然後記得 `start..end` 語法。 ## HTTP - GET 你可以使用 `cargo add ureq` 去安裝 `ureq`,或是改變 `Cargo.toml`。 ```toml [dependencies] ureq = "2.10.1" ``` ```rust fn main() -> Result<(), ureq::Error> { let body = ureq::get("https://example.org") .call()? .into_string()?; println!("{body}"); Ok(()) } ``` - 可以注意到在 `main()` 回傳值是 `Result` 的集合,包含 `()` 與 `ureq::Error`。 - 最後一行的 `Ok(())` 不能加上 `;`,因為這代表回傳值。 ## 輸入輸出同時使用參考傳遞 ```rust use std::io; fn main() { let mut str = String::new(); println!("Type some message: "); io::stdin() .read_line(&mut str) .expect("Please enter text."); borrow(&str); println!("Again! {}!", str.trim()); } fn borrow(str: &String) -> () { println!("First attempt: {}.", str.trim()) } ``` # 變數(Variable) ### mut 變數預設是不可變的(immutable),也就是一旦我們給予變數一個數值,該數值就不會被改變。 要讓變數成為可變的話,我們可以在變數名稱前面加上 `mut`。 ```rust let apple = 5; // 不可變的 let mut banana = 5; // 可變的 ``` 如果要讓可變變數重新賦值的話: ```rust let mut x = 5; println!("x 的數值為:{x}"); x = 6; println!("x 的數值為:{x}"); ``` ### shadow 遮蔽(Shadowing)讓我們可以重複使用變數名稱,而不必強迫我們得建立兩個不同的變數,舉例來說像是 `guess_str` 和 `guess`。 跟可變變數重新賦值很像!因此遮蔽可以想像成如果不可變變數真的要重新賦值的時候,它的操作就是這種方式。 ```rust let a = 12; println!("{a}"); let a = 24; println!("{a}"); ``` ```shell $ cargo run 12 24 ``` # 字串(String) String 是個標準函式庫提供的字串型別,這是可增長的 `UTF-8` 編碼文字。 ```rust use std::io; fn main() { let mut str = String::new(); io::stdin() .read_line(&mut str) .expect("請輸入數字"); println!("{str}"); } ``` ### `trim()` 去除開頭與結尾的任何空白字元。 > [!NOTE] > 需要使用 `let var: i32` 來告訴 Rust 我們想使用的確切數字型別。 當使用者按下 `enter` 時,字串結尾就會加上換行字元。舉例來說,如果使用者輸入 `5` 並按下 `enter` 的話,`guess` 看起來會像這樣:`5\n`。`\n` 指的是「換行(newline)」,這是按下 `enter` 的結果(在 Windows 按下 `enter` 的結果會是輸入和換行 `\r\n`)。`trim` 方法能去除 `\n` 或 `\r\n`,讓結果只會是 `5`。 ```rust! use std::io; fn main() { let mut var = String::new(); io::stdin() .read_line(&mut var) .expect("error!"); println!("this is your input: {var}."); let var: u32 = var.trim().parse().expect("Not a number!"); println!("this is your input but integer: {var}."); } ``` ### `as_bytes()` `as_bytes` 方法將 `String` 轉換成一個位元組陣列。型別是不可變參考字節切片 `&[u8]`。 ```rust fn main() { let s: String = String::from("hello"); let bytes: &[u8] = s.as_bytes(); println!("{:?}", bytes); } ``` ```shell $ cargo run [104, 101, 108, 108, 111] ``` # 資料型別 每個數值在 Rust 中都屬於某種**資料型別**,這告訴 Rust 何種資料被指定,好讓它能妥善處理資料。我們將討論兩種資料型別子集:純量(scalar)與複合(compound)。 請記住 Rust 是一門**靜態型別**語言,這代表它必須在編譯時知道所有變數的型別。編譯器通常能依據數值與我們使用的方式推導出我們想使用的型別。但有時候如果多種型別都有可能時,像是第二章的[「將猜測的數字與祕密數字做比較」](https://rust-lang.tw/book-tw/ch02-00-guessing-game-tutorial.html#%E5%B0%87%E7%8C%9C%E6%B8%AC%E7%9A%84%E6%95%B8%E5%AD%97%E8%88%87%E7%A5%95%E5%AF%86%E6%95%B8%E5%AD%97%E5%81%9A%E6%AF%94%E8%BC%83)用到的 `parse` 將 `String` 轉換成數字時,我們就需要像這樣加上型別詮釋: ```! let guess: u32 = "42".parse().expect("這不是數字!"); ``` 如果我們沒有像上列程式碼這樣加上型別詮釋 `: u32` 的話,Rust 將會顯示以下錯誤訊息。這表示編譯器需要我們給予更多資訊才能夠知道我們想用何種型別: ```shell! $ 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("這不是數字!"); | ^^^^^ | help: consider giving `guess` an explicit type | 2 | let guess: _ = "42".parse().expect("這不是數字!"); | +++ For more information about this error, try `rustc --explain E0282`. error: could not compile `no_type_annotations` due to previous error ``` 你將會看到其他資料型別的各種型別詮釋。 ## 純量型別 ### 整數型別 **純量**型別代表單一數值。Rust 有四種主要純量型別:整數、浮點數、布林以及字元。你應該在其他程式語言就看過它們了,讓我們來看看它們在 Rust 是怎麼使用的: #### 表格 3-1:Rust 中的整數型別 | 長度 | 帶號 | 非帶號 | | -------- | ------- | ------- | | 8 位元 | `i8` | `u8` | | 16 位元 | `i16` | `u16` | | 32 位元 | `i32` | `u32` | | 64 位元 | `i64` | `u64` | | 128 位元 | `i128` | `u128` | | 系統架構 | `isize` | `usize` | 每個變體都可以是帶號或非帶號的,並且都有明確的大小。**帶號**與**非帶號**的區別是數字能不能有負數,換句話說就是數字能否帶有正負符號,如果沒有的話那就只會出現正整數而已。就像在紙上寫數字一樣:當我們需要考慮符號時,我們就會在數字前面加上正負號;但如果我們只在意正整數的話,那它可以不帶符號。帶號數字是以[二補數](https://en.wikipedia.org/wiki/Two%27s_complement)的方式儲存。 每一帶號變體可以儲存的數字範圍包含從 $-(2n - 1)$ 到 $2n-1-1$ 以內的數字,$n$ 就是該變體佔用的位元大小。所以一個 `i8` 可以儲存的數字範圍就是從 $-(27)$ 到 $27- 1$,也就是 $-128$ 到 $127$。而非帶號可以儲存的數字範圍則是從 $0$ 到 $2n-1$,所以 `u8` 可以儲存的範圍是從 $0$ 到 $28-1$,也就是 $0$ 到 $255$。 另外,`isize` 與 `usize` 型別則是依據你程式運行的電腦架構來決定大小,所以上方表格才用「系統架構」來表示長度:如果你在 64 位元架構上的話就是 64 位元;如果你是 32 位元架構的話就是 32 位元。 你可以用表格 3-2 列的格式來寫出整數字面值(literals)。能適用於數種數字型別的數字字面值都允許在最後面加上型別,比如說用 `57u8` 來指定型別。數字字面值也可以加上底線 `_` 分隔方便閱讀,比如說 `1_000` 其實就和指定 `1000` 的數值一樣。 #### 表格 3-2:Rust 中的整數字面值 | 數字字面值 | 範例 | | ------------------ | ------------- | | 十進制 | `98_222` | | 十六進制 | `0xff` | | 八進制 | `0o77` | | 二進制 | `0b1111_0000` | | 位元組(僅限`u8`) | `b'A'` | 所以你該用哪些整數型別呢?如果你不確定的話,Rust 預設的型別是很好的起始點:整數型別預設是 `i32`。而你會用到 `isize` 或 `usize` 的主要時機是作為某些集合的索引。 > [!NOTE] 整數溢位 > 假設你有個變數型別是 `u8` 可以儲存 0 到 255 的數值。如果你想要改變變數的值超出這個範圍的話,比方說像是 256,那麼就會發生**整數溢位**,這會產生兩種不同的結果。如果你是在除錯模式編譯的話,Rust 會包含整數溢位的檢查,造成你的程式在執行時**恐慌(panic)**。Rust 使用恐慌來表示程式因錯誤而結束,我們會在第九章的[「對無法復原的錯誤使用 `panic!`」](https://rust-lang.tw/book-tw/ch09-01-unrecoverable-errors-with-panic.html)段落討論更多造成恐慌的細節。 > > 當你是在發佈模式下用 `--release` 來編譯的話,Rust 則**不會**加上整數溢位的檢查而造成恐慌。相反地,如果發生整數溢位的話,Rust 會作出**二補數包裝**的動作。簡單來說,超出最大值的數值可以被**包裝**成該型別的最低數值。以 `u8` 為例的話,256 會變成 0、257 會變成 1,以此類推。程式不會恐慌,但是該變數可能會得到一個不是你原本預期的數值。通常依靠整數溢位的行為仍然會被視為邏輯錯誤。 > > 要顯式處理可能的溢位的話,你可以使用以下標準函式庫中基本型別提供的一系列方法: > > - 將所有操作用 `wrapping_*` 方法包裝,像是 `wrapping_add`。 > - 使用 `checked_*` 方法,如果有溢位的話其會回傳 `None` 數值。 > - 使用 `overflowing_*` 方法,其會回傳數值與一個布林值來顯示是否有溢位發生。 > - 屬於 `saturating_*` ,讓數值溢位時保持在最小或最大值。 ### 浮點數型別 Rust 還有針對有小數點的**浮點數**提供兩種基本型別:`f32` 和 `f64`,分別佔有 32 位元與 64 位元的大小。而預設的型別為 `f64`,因為現代的電腦處理的速度幾乎和 `f32` 一樣卻還能擁有更高的精準度。所有的浮點數型別都是帶號的(signed)。 以下為展示浮點數的範例: 檔案名稱:src/main.rs ```rust fn main() { let x = 2.0; // f64 let y: f32 = 3.0; // f32 } ``` 浮點數是依照 IEEE-754 所定義的,`f32` 型別是單精度浮點數,而 `f64` 是倍精度浮點數。 ### 數值運算 Rust 支援你所有想得到的數值型別基本運算:加法、減法、乘法、除法和取餘。整數除法會取最接近零的下界數值。以下程式碼展示出如何在 `let` 陳述式使用這些運算: 檔案名稱:src/main.rs ```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; } ``` 每一個陳述式中的表達式都使用了一個數學運算符號並計算出一個數值出來,賦值給該變數。[附錄 B](https://rust-lang.tw/book-tw/appendix-02-operators.html) 有提供列表列出 Rust 所提供的所有運算子。 ### 布林型別 如同其他多數程式語言一樣,Rust 中的布林型別有兩個可能的值:`true` 和 `false`。布林值的大小為一個位元組。要在 Rust 中定義布林型別的話用 `bool`,如範例所示: 檔案名稱:src/main.rs ```rust fn main() { let t = true; let f: bool = false; // 型別詮釋的方式 } ``` 布林值最常使用的方式之一是作為條件判斷,像是在 `if` 表達式中使用。我們將會在[「控制流程」](https://rust-lang.tw/book-tw/ch03-05-control-flow.html#control-flow)段落介紹如何在 Rust 使用 `if` 表達式。 ### 字元型別 Rust 的 `char` 型別是最基本的字母型別,以下程式碼顯示了使用它的方法: 檔案名稱:src/main.rs ```rust fn main() { let c = 'z'; let z: char = 'ℤ'; // 明確標註型別的寫法 let heart_eyed_cat = '😻'; } ``` 注意到 `char` 字面值是用單引號賦值,宣告字串字面值時才是用雙引號。Rust 的 `char` 型別大小為四個位元組並表示為一個 Unicode 純量數值,這代表它能擁有的字元比 ASCII 還來的多。舉凡標音字母(Accented letters)、中文、日文、韓文、表情符號以及零長度空格都是 Rust `char` 的有效字元。Unicode 純量數值的範圍包含從 `U+0000` 到 `U+D7FF` 以及 `U+E000` 到 `U+10FFFF`。但是一個「字元」並不是真正的 Unicode 概念,所以你對於什麼是一個「字元」的看法可能會和 Rust 的 `char` 不一樣。我們將會在第八章的[「透過字串儲存 UTF-8 編碼的文字」](https://rust-lang.tw/book-tw/ch08-02-strings.html#storing-utf-8-encoded-text-with-strings)來討論此議題。 ## 複合型別 複合型別可以組合數個數值為一個型別,Rust 有兩個基本複合型別:元組(tuples)和陣列(arrays)。 ### 元組(Tuple)型別 > [!IMPORTANT] 元組輸出規則 > 要輸出元組資料,就要先**解構**元組。 > [!NOTE] > 沒有任何數值的元組有一種特殊的名稱叫做**單元型別(Unit)**,其數值與型別都寫作 `()`,通常代表一個空的數值或空的回傳型別。表達式要是沒有回傳任何數值的話,它們就會隱式回傳單元型別。 **元組**是個將許多不同型別的數值合成一個複合型別的常見方法。元組擁有固定長度:一旦宣告好後,它們就無法增長或縮減。 此變數 `tup` 就是整個元組,因為一個元組就被視為單一複合元素。要拿到元組中的每個獨立數值的話,我們可以用模式配對(pattern matching)來解構一個元組的數值,如以下所示: ```rust! fn main() { let tup: (i32, f64, u8) = (500, 3.0, 1); let (x, y, z) = tup; println!("{x}, {y}, {z}"); } ``` 此程式先是建立了一個元組然後賦值給 `tup`,接著它用模式配對和 let 將 tup 拆成三個個別的變數 `x`、`y` 和 `z`。這就叫做**解構(destructuring)**,因為它將單一元組拆成了三個部分。最後程式將 `y` 的值印出來,也就是 `6.4`。 我們也可以直接用句號(`.`)再加上數值的索引來取得元組內的元素。舉例來說: ```rust! fn main() { let tup: (i32, f64, u8) = (500, 3.0, 1); let x = tup.0; let y = tup.1; let z = tup.2; println!("{x}, {y}, {z}") } ``` > [!WARNING] 元組輸出注意 > 上述能用 `.` 來索引元組的資料,但都是在分配到新的變數這個前提下,我們不能直接對 `tup` 進行索引然後輸出。 > > ```rust! > fn main() { > let tup: (i32, f64, u8) = (500, 3.0, 1); > println!("{tup.0}, {tup.1}, {tup.2}"); > } > ``` ### 陣列(Array)型別 另一種取得數個數值集合的方法是使用**陣列**。和元組不一樣的是,陣列中的每個型別必須是一樣的。和其他語言的陣列不同,Rust 的陣列是固定長度的。 > **向量**是標準函式庫提供的集合型別,類似於陣列但允許變更長度大小。如果你不確定該用陣列或向量的話,通常你應該用向量就好。 > [!IMPORTANT] > 陣列跟元組一樣是複合型別,但跟元組不一樣的是陣列不能存入不同資料型別。 > > ```rust! > fn main() { > let arr = [500, 3.0, 1]; > > let x = arr[0]; > let y = arr[1]; > let z = arr[2]; > > println!("{x}, {y}, {z}"); > } > ``` > > ```shell! > $ cargo run > error[E0308]: mismatched types > --> src/main.rs:2:21 > | > 2 | let arr = [500, 3.0, 1]; > | ^^^ expected integer, found floating-point number > > For more information about this error, try `rustc --explain E0308`. > error: could not compile `hello_cargo` (bin "hello_cargo") due to 1 previous error > ``` > [!IMPORTANT] 跟元組一樣,不能直接輸出陣列內容 > > ```rust! > fn main() { > let arr = [500, 3, 1]; > println!("{arr[0]}"); > } > ``` > > ```shell! > $ cargo run > error: invalid format string: expected `'}'`, found `'['` > --> src/main.rs:3:19 > | > 3 | println!("{arr[0]}"); > | - ^ expected `'}'` in format string > | | > | because of this opening brace > | > = note: if you intended to print `{`, you can escape it using `{{` > ``` ```rust fn main() { let arr = [1, 2, 3, 4]; let x = arr[0]; println!("{x}"); } ``` 要詮釋陣列型別的話,你可以在中括號寫出型別和元素個數,並用分號區隔開來,如以下所示: ```rust let a: [i32; 5] = [1, 2, 3, 4, 5]; ``` # 例外處理 當發生例外時,有以下幾種方式能處理: ### `.expect()` 當發生例外時,呼叫 `expect` 拋出錯誤資訊,並結束程式。 ```rust! use std::io; fn main() { let mut str = String::new(); io::stdin() .read_line(&mut str) .expect("讀取失敗"); let str: u32 = str.trim().parse().expect("請輸入數字"); println!("{str}"); } ``` ### `match` 當發生例外時,利用 `match` 將 Result 的列舉條件依序配對。 > [!NOTE] > 底線 `_` 是個捕獲數值,我們說我們想要配對到所有的 `Err` 數值,無論其中有什麼資訊在裡面。 ```rust! use std::io; fn main() { let mut str = String::new(); io::stdin() .read_line(&mut str) .expect("讀取失敗"); let str: u32 = match str.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("{str}"); } ``` # 列舉(Enum) **列舉(enumerations)**,常稱為 `enums`。列舉是種可能有數種狀態其中之一的型別,而每種可能的狀態我們稱之為列舉的**變體(variants)**。 ## `Result` `Result` 是種**列舉(enumerations)**。`Result` 的[[#變體]]有 `Ok` 和 `Err`。`Ok` 變體指的是該動作成功完成,且 `Ok` 內部會包含成功產生的數值。而 `Err` 變體代表動作失敗,且 `Err` 會包含該動作如何與為何會失敗的資訊。 ### `Result` 的變體 1. `Ok()` 2. `Err()` # 函式 Rust 不在乎你的函式是在哪裡定義的,只需要知道它定義在作用域的某處,且能被呼叫者看到就好。 ```rust fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("另一支函式。"); } ``` ## 參數 **我們也可以定義函式成擁有參數(parameters)的,這是函式簽名(signatures)中特殊的變數。當函式有參數時,你可以提供那些參數的確切數值。嚴格上來說,我們傳遞的數值會叫做引數(arguments)**。 但為了方便起見,通常大家不太會去在意兩者的區別。雖然函式定義時才叫參數,傳遞數值時叫做引數,但很多情況下都能被交換使用。 以下的  `another_function`  是加上參數後的版本: ```rust fn main() { println!("Hello, world!"); another_function(5); } fn another_function(x: i32) { println!("x 的數值為:{x}"); } ``` ## 陳述式與表達式 函式本體是由一系列的陳述式(statements)並在最後可以選擇加上表達式(expression)來組成。 - **陳述式**(Statements)是進行一些動作的指令,且不回傳任何數值。 - **表達式**(Expressions)則是計算並產生數值。讓我們來看一些範例: ```rust fn main() { let y = 6; } ``` `let y = 6;` 就是個陳述式。 此函式定義也是陳述式,整個範例本身就是一個陳述式。 陳述式不會回傳數值,因此你無法將 let 陳述式賦值給其他變數。如同以下程式碼所做的,你將會得到一個錯誤: ```rust fn main() { let x = (let y = 6); } ``` `let y = 6` 陳述式不回傳數值,所以 `x` 得不到任何數值。這就和其他語言有所不同,像是 C 或 Ruby,通常它們的賦值仍能回傳所得到的值。在那些語言,你可以寫 `x = y = 6` 同時讓 `x` 與 `y` 都取得 `6`,但在 Rust 就不行。 表達式則會運算出一個數值,並組合成你大部分所寫的 Rust 程式。先想想看一個數學運算比如  `5 + 6`,這就是個會算出  `11`  的表達式。表達式可以是陳述式的一部分:在範例 3-1 中  `let y = 6;`  的  `6`  其實就是個算出  `6`  的表達式。呼叫函式也可以是表達式、呼叫巨集也是表達式、我們用  `{}`  產生的作用域也是表達式。舉例來說: ```rust fn main() { let x = 5; let y = { let x = 3; x + 1 }; println!("y 的數值為:{y}"); } ``` 此表達式: ```rust { let x = 3; x + 1 } ``` 就是一個會回傳  `4`  的區塊,此值再用  `let`  陳述式賦值給  `y`。請注意到  `x + 1`  這行沒有加上分號,它和你目前看到的寫法有點不同,因為表達式結尾不會加上分號。如果你在此表達式加上分號的話,它就不會回傳數值。在我們繼續探討函式回傳值與表達式的同時請記住這一點。 ## 函式回傳值 ```rust fn main() { let x = plus_one(5); println!("x 的數值為:{x}"); } fn plus_one(x: i32) -> i32 { x + 1 } ``` 執行此程式會顯示  `x 的數值為:6`,但如果我們在最後一行  `x + 1`  加上分號的話,就會將它從表達式變為陳述式。我們就會得到錯誤。 ```rust fn main() { let x = plus_one(5); println!("x 的數值為:{x}"); } fn plus_one(x: i32) -> i32 { x + 1; } ``` 編譯此程式就會產生以下錯誤: ```shell! $ cargo run Compiling functions v0.1.0 (file:///projects/functions) error[E0308]: mismatched types --> src/main.rs:7:24 | 7 | fn plus_one(x: i32) -> i32 { | -------- ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 8 | x + 1; | - help: removing this semicolon For more information about this error, try `rustc --explain E0308`. error: could not compile `functions` due to previous error ``` 錯誤訊息  `mismatched types`  就告訴了我們此程式碼的核心問題。`plus_one`  的函式定義說它會回傳  `i32`  但是陳述式不會回傳任何數值。我們用單元型別  `()`  表示不會回傳任何值。因此沒有任何值被回傳,這和函式定義相牴觸,最後產生錯誤。在此輸出結果,Rust 提供了一道訊息來協助解決問題:它建議移除分號,這樣就能修正錯誤。 > [!NOTE] 但如果是用 `return` 的話就可以使用陳述式 > > ```shell > fn five() -> i32 { > return 5 + 1; > } > > fn main() { > let x: i32 = five(); > > println!("x 的數值為:{x}"); > } > ``` > > ```shell! > $ cargo run > Compiling learn v0.1.0 (/home/lturret/learn) > Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s > Running `target/debug/learn` > x 的數值為:6 > ``` > > 然後還可以注意這裡要不要加 `;` 都沒有差。 # 補充 - 基準化分析(benchmarking) - [關鍵字(keywords)](https://rust-lang.tw/book-tw/appendix-01-keywords.html)