函式
函式在 Rust 程式碼中無所不在。你已經見過一個語言最重要的函式了:main
函式是許多程式的入口點。此外你也看到了 fn
關鍵字能讓你宣告新的函式。
Rust 程式碼使用 snake case 式作為函式與變數名稱的慣例風格。所有的字母都是小寫,並用底線區隔單字。以下是一支包含函式定義範例的程式:
檔案名稱:src/main.rs
fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("另一支函式。"); }
我們在 Rust 中定義函式是先從 fn
開始,再加上函式名稱和一組括號,大括號告訴編譯器函式本體的開始與結束位置。
我們可以輸入函式的名稱並加上括號來呼叫任何我們定義過的函式。因為 another_function
已經在程式中定義了,他就可以在 main
函式中呼叫。注意到我們是在原始碼中的 main
函式之後定義 another_function
的,我們當然也可以把它定義在前面。Rust 不在乎你的函式是在哪裡定義的,只需要知道它定義在作用域的某處,且能被呼叫者看到就好。
讓我們開啟一個新的專案叫做 functions 來進一步探索。請將 another_function
範例放入 src/main.rs 然後執行它。你應該會看到以下輸出:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/functions`
Hello, world!
另一支函式。
程式碼會按照 main
函式中的順序執行。首先,「Hello, world!」的訊息會先顯示出來,再來才會呼叫 another_function
並印出它的訊息。
參數
我們也可以定義函式成擁有參數(parameters)的,這是函式簽名(signatures)中特殊的變數。當函式有參數時,你可以提供那些參數的確切數值。嚴格上來說,我們傳遞的數值會叫做引數(arguments)。但為了方便起見,通常大家不太會去在意兩者的區別。雖然函式定義時才叫參數,傳遞數值時叫做引數,但很多情況下都能被交換使用。
以下的 another_function
是加上參數後的版本:
檔案名稱:src/main.rs
fn main() { another_function(5); } fn another_function(x: i32) { println!("x 的數值為:{x}"); }
嘗試執行程式的話,你應該會看到以下輸出結果:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/functions`
x 的數值為:5
宣告 another_function
時有一個參數叫做 x
,而 x
的型別被指定為 i32
。當我們傳遞 5
給 another_function
時,println!
巨集會將 5
置於格式化字串中的大括號內 x
的位置。
在函式簽名中,你必須宣告每個參數的型別,這是 Rust 刻意做下的設計決定:在函式定義中要求型別詮釋,代表編譯器幾乎不需要你在其他地方再提供資訊才能知道你要使用什麼型別。而且如果編譯器能知道函式預期的型別的話,它還能夠給予更有幫助的錯誤訊息。
如果要定義函式擁有數個參數時,會用逗號區隔開來,像這樣:
檔案名稱:src/main.rs
fn main() { print_labeled_measurement(5, 'h'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("測量值爲:{value}{unit_label}"); }
此範例建立了一個有兩個參數的函式 print_labeled_measurement
,第一個參數叫做 value
而型別爲 i32
,第二個參數叫做 unit_label
而型別爲 char
。接著函式會印出包含 value
與 unit_label
的文字。
讓我們試著執行此程式碼,請覆蓋你的專案 functions 內的 src/main.rs 檔案內容為以上範例,然後用 cargo run
執行程式:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/functions`
測量值爲:5h
因為我們呼叫函式時,將 5
給了 value
且將 'h'
給了 unit_label
,程式輸出就會包含這些數值。
陳述式與表達式
函式本體是由一系列的陳述式(statements)並在最後可以選擇加上表達式(expression)來組成。目前我們只講了沒有用到表達式做結尾的函式。由於 Rust 是門基於表達式(expression-based)的語言,知道這樣的區別是很重要的。其他語言通常沒有這樣的區別,所以現在讓我們來看看陳述式和表達式有什麼不同,以及它們怎麼影響函式本體。
- 陳述式(Statements)是進行一些動作的指令,且不回傳任何數值。
- 表達式(Expressions)則是計算並產生數值。讓我們來看一些範例:
我們其實已經使用了很多次陳述式與表達式。建立一個變數然後用 let
關鍵字賦值給它就是一道陳述式。在範例 3-1 中的 let y = 6;
就是個陳述式。
檔案名稱:src/main.rs
fn main() { let y = 6; }
此函式定義也是陳述式,整個範例本身就是一個陳述式。
陳述式不會回傳數值,因此你無法將 let
陳述式賦值給其他變數。如同以下程式碼所做的,你將會得到一個錯誤:
檔案名稱:src/main.rs
fn main() {
let x = (let y = 6);
}
當你執行此程式時,你就會看到這樣的錯誤訊息:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^
error: expected expression, found statement (`let`)
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: variable declaration using `let` is a statement
error[E0658]: `let` expressions in this position are unstable
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
warning: unnecessary parentheses around assigned value
--> src/main.rs:2:13
|
2 | let x = (let y = 6);
| ^ ^
|
= note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
|
2 - let x = (let y = 6);
2 + let x = let y = 6;
|
For more information about this error, try `rustc --explain E0658`.
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` due to 3 previous errors; 1 warning emitted
let y = 6
陳述式不回傳數值,所以 x
得不到任何數值。這就和其他語言有所不同,像是 C 或 Ruby,通常它們的賦值仍能回傳所得到的值。在那些語言,你可以寫 x = y = 6
同時讓 x
與 y
都取得 6
,但在 Rust 就不行。
表達式則會運算出一個數值,並組合成你大部分所寫的 Rust 程式。先想想看一個數學運算比如 5 + 6
,這就是個會算出 11
的表達式。表達式可以是陳述式的一部分:在範例 3-1 中 let y = 6;
的 6
其實就是個算出 6
的表達式。呼叫函式也可以是表達式、呼叫巨集也是表達式、我們用 {}
產生的作用域也是表達式。舉例來說:
檔案名稱:src/main.rs
fn main() { let x = 5; let y = { let x = 3; x + 1 }; println!("y 的數值為:{y}"); }
此表達式:
{
let x = 3;
x + 1
}
就是一個會回傳 4
的區塊,此值再用 let
陳述式賦值給 y
。請注意到 x + 1
這行沒有加上分號,它和你目前看到的寫法有點不同,因為表達式結尾不會加上分號。如果你在此表達式加上分號的話,它就不會回傳數值。在我們繼續探討函式回傳值與表達式的同時請記住這一點。
函式回傳值
函式可以回傳數值給呼叫它們的程式碼,我們不會為回傳值命名,但我們必須用箭頭(->
)來宣告它們的型別。在 Rust 中,回傳值其實就是函式本體最後一行的表達式。你可以用 return
關鍵字加上一個數值來提早回傳函式,但多數函式都能用最後一行的表達式作為數值回傳。以下是一個有回傳數值的函式範例:
檔案名稱:src/main.rs
fn five() -> i32 { 5 } fn main() { let x = five(); println!("x 的數值為:{x}"); }
在 five
函式中沒有任何函式呼叫、巨集甚至是 let
陳述式,只有一個 5
。這在 Rust 中完全是合理的函式。請注意到函式的回傳型別也有指明,就是 -> i32
。嘗試執行此程式的話,輸出結果就會像是這樣:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/functions`
x 的數值為:5
five
中的 5
就是函式的回傳值,這就是為何回傳型別是 i32
。讓我們進一步研究細節,這邊有兩個重要的地方:首先這行 let x = five();
顯示了我們用函式的回傳值作為變數的初始值。因為函式 five
回傳 5
,所以這行和以下程式碼相同:
#![allow(unused)] fn main() { let x = 5; }
再來,five
函式沒有參數但有定義回傳值的型別。所以函式本體只需有一個 5
就好,不需加上分號,這樣就能當做表達式回傳我們想要的數值。
讓我們再看另一個例子:
檔案名稱:src/main.rs
fn main() { let x = plus_one(5); println!("x 的數值為:{x}"); } fn plus_one(x: i32) -> i32 { x + 1 }
執行此程式會顯示 x 的數值為:6
,但如果我們在最後一行 x + 1
加上分號的話,就會將它從表達式變為陳述式。我們就會得到錯誤:
檔案名稱:src/main.rs
fn main() {
let x = plus_one(5);
println!("x 的數值為:{x}");
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
編譯此程式就會產生以下錯誤:
$ 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 提供了一道訊息來協助解決問題:它建議移除分號,這樣就能修正錯誤。