iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0

Rust 的函數和其他語言基本都差不多,基本結構可以分成兩個部分來理解:函數簽名(function signature) 以及函數本體(function body)。

函數簽名

fn function_name(parameters) -> return_type {
    // function body
}

函數簽名就是除了大括號裡面的部分,簡單說它定義了函數的輸入參數和輸出類型。

  • fn 是函數的關鍵字,後面 function_name 自行定義函數名稱,Rust 裡面變數和函數的命名慣用 snake case,不用的話編譯器不會報錯但會 warning ,這是因為 Rust 提供了內建的靜態分析功能,不需要像 JavaScript 那樣安裝額外工具(如 EsLint)。
  • parameters 是 0 或多個函數的參數,每個參數都必須指定型別
  • return_type 之前也有提到過,代表這個函數回傳值的型別,用 -> 指定,如果沒有回傳值這部分可以省略。
  • {} 裡面定義函數的行為,表示函數的範圍還有定義其中局部變數的作用域。另外,大括號內最後一個表達式會自動成為返回值
  • 函數的位置沒有前後關係,Rust 不在意函數的定義順序,只要函數在呼叫時已定義於可見的範圍內,無論定義在何處,程式都能正常編譯。
    例如下面兩者意義上是相同的。
fn sum(x: i32, y: i32) -> i32 {
    return x + y;
}

fn main() {
    let result = sum(5, 6);
    println!("{}", result);
}
fn main() {
    let result = sum(5, 6);
    println!("{}", result);
}

fn sum(x: i32, y: i32) -> i32 {
    return x + y;
}

函數本體

再來介紹 {} 裡面,也就是函數本體,邏輯的實作以及細節。
裡面會由一系列的陳述式(statements)並在最後可以選擇加上表達式(expression)來組成。Rust 是門基於表達式(expression-based)的語言,幾乎所有程式區塊和流程控制結構都能回傳值,讓 Rust 的語法非常靈活,有些語言沒有那麼在意兩者差別,但在 Rust 需要明確區分兩者,理解兩者差異對於撰寫簡潔且符合 Rust 語法的程式碼非常重要。

  • 陳述式(Statements)是進行一些動作的指令,且不回傳任何數值
  • 表達式(Expressions)則是計算並產生數值,結尾不會加上分號,如果加上分號就不會回傳值了!

所以前面範例裡面 sum 的 return 是可以省略的。

fn sum(x: i32, y: i32) -> i32 {
    x + y // 這是表達式
}

fn main() {
    let result = sum(5, 6); // 這是陳述式
    println!("{}", result);
} // 整個 fn 的區塊也可以視為表達式

另外前一篇有提到 if 的賦值其實也和陳述式、表達式有關,我們重新看一下當初提到的例子

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("數字結果為:{number}");
}

可以注意到 if-else 大括號裡面的數字後面也沒有分號,如果加上分號會變成怎樣呢?

fn main() {
    let condition = true;
    let number = if condition { 5; } else { 6; };

    println!("數字結果為:{number}");
}

答案是直接報錯。

$ cargo run
error[E0277]: `()` doesn't implement `std::fmt::Display`
 --> src/main.rs:5:21
  |
5 |     println!("數字結果為:{number}");
  |                           ^^^^^^^^ `()` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for `()`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

接下來解析一下他的錯誤 error[E0277]: () doesn't implement std::fmt::Display
會有這個錯誤的原因是,預期拿到的 number 其實是一個 () ,也就是介紹元組與陣列提到的單元型別,代表沒有回傳值。

到這邊可以看出來,if-else 大括號裡面其實和函數的大括號有相同的行為,原本的 5 和 6 是一種表達式,在大括號內的最後一句表達式會自動回傳值,這樣可以減少顯式的 return 語句,使得程式碼更加簡潔。因此一開始沒有分號的時候 number 會拿到回傳值 5,後面加上分號之後就不會回傳,而回傳了預設值單元型別。

結語

本篇文章介紹了 Rust 函數的結構、表達式與陳述式的區別,以及如何利用它們寫出更簡潔的程式碼,並進一步理解 if-else 賦值的寫法的細節以及能夠區別什麼時候句尾該有分號什麼時候不用。

到這篇終於把 Rust 的基礎資料型別、特性、基本語法有基礎的認識,這些基礎概念能幫助我們更流暢地進入接下來的核心內容——Rust 的所有權系統。


上一篇
Day10 - 流程控制
下一篇
Day12 - 所有權(一):基礎認識
系列文
螃蟹幼幼班:Rust 入門指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言