又到了該學習 Rust 的日子啦!(每天都是😂)
繼上次認識了不能變的變數之後,今天我們要來了解 Rust 的函數。
上一篇:01_variables
本系列主頁面:Rust繁中簡學!
繁體中文版 Rustlings:https://github.com/TimLai666/rustlings-zh-TW
安裝方法:00_intro
02_functions(函數)
老樣子,先看 README。
函數
在這裡,您將學習如何編寫函數以及 Rust 編譯器如何幫助您調試(debug)錯誤,即使是更複雜的代碼中的。
進一步了解
這次的說明 有寫跟沒寫一樣 寫得非常簡單。總之今天的主角就是函數,我們直接來看題目吧!
練習一(functions1.rs)
// functions1.rs // // 執行 `rustlings hint functions1` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { call_me(); }
Progress: [------------------------------------------------------------] 0/96 (0.0 %) Progress: [>-----------------------------------------------------------] 1/96 (1.0 %) Progress: [#>----------------------------------------------------------] 2/96 (2.1 %) Progress: [#>----------------------------------------------------------] 3/96 (3.1 %) Progress: [##>---------------------------------------------------------] 4/96 (4.2 %) Progress: [###>--------------------------------------------------------] 5/96 (5.2 %) Progress: [###>--------------------------------------------------------] 6/96 (6.2 %) Progress: [####>-------------------------------------------------------] 7/96 (7.3 %) Progress: [#####>------------------------------------------------------] 8/96 (8.3 %) ⚠️ Compiling of exercises/02_functions/functions1.rs failed! Please try again. Here's the output: error[E0425]: cannot find function `call_me` in this scope --> exercises/02_functions/functions1.rs:8:5 | 8 | call_me(); | ^^^^^^^ not found in this scope error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0425`. Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.
這題呼叫了一個函數 call_me
,但卻沒有在程式碼中定義它。
那我們應該只要自己寫一個 call_me
函數就行。
// functions1.rs // // 執行 `rustlings hint functions1` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { call_me(); } fn call_me() { println!("Hi!"); }
我把 call_me
函數定義成會簡單地印出「Hi!」。
進度: [------------------------------------------------------------] 0/96 (0.0 %) 進度: [>-----------------------------------------------------------] 1/96 (1.0 %) 進度: [#>----------------------------------------------------------] 2/96 (2.1 %) 進度: [#>----------------------------------------------------------] 3/96 (3.1 %) 進度: [##>---------------------------------------------------------] 4/96 (4.2 %) 進度: [###>--------------------------------------------------------] 5/96 (5.2 %) 進度: [###>--------------------------------------------------------] 6/96 (6.2 %) 進度: [####>-------------------------------------------------------] 7/96 (7.3 %) 進度: [#####>------------------------------------------------------] 8/96 (8.3 %) ✅ 成功運行 exercises/02_functions/functions1.rs! 🎉 🎉 代碼正在編譯! 🎉 🎉 輸出: ==================== Hi! ==================== 您可以繼續進行此練習, 或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習: 3 | // 執行 `rustlings hint functions1` 或使用 `hint` watch 子命令來獲取提示。 4 | 5 | // I AM NOT DONE 6 | 7 | fn main() { 歡迎來到 watch 模式!您可以輸入 'help' 來獲取此處可用命令的概覽。
編譯成功。
從這裡我們可以看到,Rust 的函數使用 fn
關鍵字來宣告,並且像大部分的程式語言一樣,用一對大括號來括出函數的範圍(如果你只有寫過 Python,可能就需要習慣一下)。而 fn 其實就是 function 的縮寫,所以應該滿好記的。
練習二(functions2.rs)
// functions2.rs // // 執行 `rustlings hint functions2` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { call_me(3); } fn call_me(num:) { for i in 0..num { println!("鈴鈴!第 {} 次呼叫", i + 1); } }
進度: [#####>------------------------------------------------------] 8/96 (8.3 %) 進度: [#####>------------------------------------------------------] 9/96 (9.4 %) ⚠️ 編譯 exercises/02_functions/functions2.rs 失敗!請再試一次。以下是輸出: error: expected type, found `)` --> exercises/02_functions/functions2.rs:11:16 | 11 | fn call_me(num:) { | ^ expected type error[E0425]: cannot find value `num` in this scope --> exercises/02_functions/functions2.rs:12:17 | 12 | for i in 0..num { | ^^^ not found in this scope | help: you might have meant to write `.` instead of `..` | 12 - for i in 0..num { 12 + for i in 0.num { | error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0425`.
從錯誤訊息可以看到,我們應該要幫 fn call_me(num:)
的 num 定義資料型態。
我們給它 u8(一定要注意每種長度整數支援的數字範圍,超過的話會出錯。這裡因為 num
最大只到 3,所以用 u8 就綽綽有餘)。
// functions2.rs // // 執行 `rustlings hint functions2` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { call_me(3); } fn call_me(num: u8) { for i in 0..num { println!("鈴鈴!第 {} 次呼叫", i + 1); } }
進度: [#####>------------------------------------------------------] 9/96 (9.4 %) ✅ 成功運行 exercises/02_functions/functions2.rs! 🎉 🎉 代碼正在編譯! 🎉 🎉 輸出: ==================== 鈴鈴!第 1 次呼叫 鈴鈴!第 2 次呼叫 鈴鈴!第 3 次呼叫 ==================== 您可以繼續進行此練習, 或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習: 3 | // 執行 `rustlings hint functions2` 或使用 `hint` watch 子命令來獲取提示。 4 | 5 | // I AM NOT DONE 6 | 7 | fn main() {
這樣就能正確執行了。
這一題一樣有值得注意的亮點,除了函數的參數需要定義型別,我們還發現 for i in 0..num
這個東西。
這是什麼意思呢?對 for 迴圈熟的朋友們應該已經感覺到了,它其實就跟 Python 裡面的 for i in range(0, num)
差不多(只是在 Python,我們通常習慣從 1 開始數到 num + 1),這個迴圈的 i 會從 0 開始累加,直到 i 不滿足 i < num 的條件(注意!這裡是 < ,不是 <=)。也就是說,當 num 為 3 的時候,i 值的變化為 0 -> 1 -> 2,迴圈共執行 3 次。
練習三(functions3.rs)
// functions3.rs // // 執行 `rustlings hint functions3` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { call_me(); } fn call_me(num: u32) { for i in 0..num { println!("鈴鈴!第 {} 次呼叫", i + 1); } }
進度: [#####>------------------------------------------------------] 9/96 (9.4 %) 進度: [######>-----------------------------------------------------] 10/96 (10.4 %) ⚠️ 編譯 exercises/02_functions/functions3.rs 失敗!請再試一次。以下是輸出: error[E0061]: this function takes 1 argument but 0 arguments were supplied --> exercises/02_functions/functions3.rs:8:5 | 8 | call_me(); | ^^^^^^^-- an argument of type `u32` is missing | note: function defined here --> exercises/02_functions/functions3.rs:11:4 | 11 | fn call_me(num: u32) { | ^^^^^^^ -------- help: provide the argument | 8 | call_me(/* u32 */); | ~~~~~~~~~~~ error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0061`.
這題的 call_me
函數需要一個資料型態為 u32
的參數,我們在呼叫它的地方(call_me();
)補上一個整數就行。
在括號中輸入 5(或其他你喜歡的數字)。
// functions3.rs // // 執行 `rustlings hint functions3` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { call_me(5); } fn call_me(num: u32) { for i in 0..num { println!("鈴鈴!第 {} 次呼叫", i + 1); } }
進度: [######>-----------------------------------------------------] 10/96 (10.4 %) ✅ 成功運行 exercises/02_functions/functions3.rs! 🎉 🎉 代碼正在編譯! 🎉 🎉 輸出: ==================== 鈴鈴!第 1 次呼叫 鈴鈴!第 2 次呼叫 鈴鈴!第 3 次呼叫 鈴鈴!第 4 次呼叫 鈴鈴!第 5 次呼叫 ==================== 您可以繼續進行此練習, 或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習: 3 | // 執行 `rustlings hint functions3` 或使用 `hint` watch 子命令來獲取提示。 4 | 5 | // I AM NOT DONE 6 | 7 | fn main() {
於是迴圈執行了 5 次。
練習四(functions4.rs)
// functions4.rs // // 這家商店正在舉行促銷活動,如果價格是偶數,您可以獲得 10 元折扣, // 但如果是奇數,則只折抵 3 元。(不用擔心函數本身的內容, // 我們目前只關心函數的簽名。如果有需要,這是一個提前了解未來練習的好方法!) // // 執行 `rustlings hint functions4` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { let original_price = 51; println!("您的售價是 {}", sale_price(original_price)); } fn sale_price(price: i32) -> { if is_even(price) { price - 10 } else { price - 3 } } fn is_even(num: i32) -> bool { num % 2 == 0 }
進度: [######>-----------------------------------------------------] 10/96 (10.4 %) 進度: [######>-----------------------------------------------------] 11/96 (11.5 %) ⚠️ 編譯 exercises/02_functions/functions4.rs 失敗!請再試一次。以下是輸出: error: expected type, found `{` --> exercises/02_functions/functions4.rs:16:30 | 16 | fn sale_price(price: i32) -> { | ^ expected type error: aborting due to 1 previous error
看來這題是個情境題,不過它的情境跟我們要做的事似乎沒有很大的關係。
錯誤訊息中的:
16 | fn sale_price(price: i32) -> { | ^ expected type
告訴我們應該在箭頭後面加上資料型態。
我們加上 i32 試試看。
// functions4.rs // // 這家商店正在舉行促銷活動,如果價格是偶數,您可以獲得 10 元折扣, // 但如果是奇數,則只折抵 3 元。(不用擔心函數本身的內容, // 我們目前只關心函數的簽名。如果有需要,這是一個提前了解未來練習的好方法!) // // 執行 `rustlings hint functions4` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { let original_price = 51; println!("您的售價是 {}", sale_price(original_price)); } fn sale_price(price: i32) -> i32 { if is_even(price) { price - 10 } else { price - 3 } } fn is_even(num: i32) -> bool { num % 2 == 0 }
進度: [######>-----------------------------------------------------] 11/96 (11.5 %) ✅ 成功運行 exercises/02_functions/functions4.rs! 🎉 🎉 代碼正在編譯! 🎉 🎉 輸出: ==================== 您的售價是 48 ==================== 您可以繼續進行此練習, 或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習: 7 | // 執行 `rustlings hint functions4` 或使用 `hint` watch 子命令來獲取提示。 8 | 9 | // I AM NOT DONE 10 | 11 | fn main() {
成功了,51 是奇數,因此折扣後的售價為 51 – 3 = 48,正確。這是什麼原理呢?
我們剛剛提到,Rust 函數必須定義傳入參數的資料型態,其實對於回傳的值也是一樣喔!
寫法是在函數的小括號 ()
後面加上一個箭頭 ->
,然後在箭頭後面寫回傳值的資料型態(跟 Python 的型別註釋 非常類似)。
至於之前幾題之所以不用這樣寫,是因為前幾題的函數沒有回傳任何東西,如果不回傳值的話就可以省略。
那麼你一定會問,這題的程式碼中又沒有 return
,為什麼會回傳值?
我們一樣去 繁體中文版《Rust 程式設計語言》 找答案。
函式回傳值
函式可以回傳數值給呼叫它們的程式碼,我們不會為回傳值命名,但我們必須用箭頭(
->
)來宣告它們的型別。在 Rust 中,回傳值其實就是函式本體最後一行的表達式。你可以用return
關鍵字加上一個數值來提早回傳函式,但多數函式都能用最後一行的表達式作為數值回傳。以下是一個有回傳數值的函式範例:檔案名稱:src/main.rs
fn five() -> i32 { 5 } fn main() { let x = five(); println!("x 的數值為:{x}"); }
從找到的資料可以得知,Rust 會自動回傳函數中最後一行表達式,不一定要用 return
。
至於什麼是「表達式」,我們有請 ChatGPT 回答:
在程式語言中,「表達式」(Expression)是一個會計算出值的程式碼片段。表達式可以簡單,如一個數值常數(例如
42
或3.14
),變數名稱(例如x
或total
),或是複雜,如涉及運算符和多個操作數的算術運算式(例如x + y
或a * b + c
)。表達式的主要特點是它們會產生值,並且可以放在任何需要值的地方,如賦值語句的右側或函數調用的參數中。此外,表達式還可以包含函數呼叫(例如
sqrt(x)
)、邏輯運算(例如x > 0 && x < 10
),或是更複雜的結構。總之,表達式是程式語言中非常基本且核心的元素,用於進行計算和數據處理。
接著進行第五個練習。
練習五(functions5.rs)
// functions5.rs // // 執行 `rustlings hint functions5` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { let answer = square(3); println!("3 的平方是 {}", answer); } fn square(num: i32) -> i32 { num * num; }
進度: [######>-----------------------------------------------------] 11/96 (11.5 %) 進度: [#######>----------------------------------------------------] 12/96 (12.5 %) ⚠️ 編譯 exercises/02_functions/functions5.rs 失敗!請再試一次。以下是輸出: error[E0308]: mismatched types --> exercises/02_functions/functions5.rs:12:24 | 12 | fn square(num: i32) -> i32 { | ------ ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression 13 | num * num; | - help: remove this semicolon to return this value error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0308`.
錯誤訊息告訴我們,fn square(num: i32) -> i32
這個函數應該回傳 i32
,實際上卻沒有。並且它也給了我們一個提示:remove this semicolon to return this value
,只要刪掉最後一句結尾的分號,就能回傳 num * num
的值。
我們照著做試試看。
// functions5.rs // // 執行 `rustlings hint functions5` 或使用 `hint` watch 子命令來獲取提示。 // I AM NOT DONE fn main() { let answer = square(3); println!("3 的平方是 {}", answer); } fn square(num: i32) -> i32 { num * num }
進度: [#######>----------------------------------------------------] 12/96 (12.5 %) ✅ 成功運行 exercises/02_functions/functions5.rs! 🎉 🎉 代碼正在編譯! 🎉 🎉 輸出: ==================== 3 的平方是 9 ==================== 您可以繼續進行此練習, 或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習: 3 | // 執行 `rustlings hint functions5` 或使用 `hint` watch 子命令來獲取提示。 4 | 5 | // I AM NOT DONE 6 | 7 | fn main() {
果然可以。今天的練習也告一段落了,來總結一下~
總結
今天學到了比較多 Rust 跟其他程式語言不一樣的地方,要特別注意。
重點如下:
- Rust 的函數用
fn
關鍵字來定義。 - 如果函數有需要傳入的參數,必須在參數名稱後面用 冒號 定義它的資料型態。
- 如果函數會回傳資料,要在小括號後面用 箭頭(
->
)定義回傳值的資料型態。 - for 迴圈中用 兩個點(
..
)表示迭代範圍,for i in 0..n
就是從 0 到 n-1 進行迭代。 - Rust 的函數會自動回傳函數中最後一行表達式(不能有分號)。
One Comment