又到了該學習 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)是一個會計算出值的程式碼片段。表達式可以簡單,如一個數值常數(例如 423.14),變數名稱(例如 xtotal),或是複雜,如涉及運算符和多個操作數的算術運算式(例如 x + ya * 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 跟其他程式語言不一樣的地方,要特別注意。

重點如下:

  1. Rust 的函數用 fn 關鍵字來定義。
  2. 如果函數有需要傳入的參數,必須在參數名稱後面用 冒號 定義它的資料型態。
  3. 如果函數會回傳資料,要在小括號後面用 箭頭->)定義回傳值的資料型態。
  4. for 迴圈中用 兩個點..)表示迭代範圍,for i in 0..n 就是從 0 到 n-1 進行迭代。
  5. Rust 的函數會自動回傳函數中最後一行表達式(不能有分號)。

Similar Posts

One Comment

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *