ch03-03-how-functions-work.mdcommit 4284e160715917a768d25265daf2db897c683065
函數(shù)在 Rust 代碼中非常普遍。你已經(jīng)見過語言中最重要的函數(shù)之一:?main
?函數(shù),它是很多程序的入口點(diǎn)。你也見過 ?fn
?關(guān)鍵字,它用來聲明新函數(shù)。
Rust 代碼中的函數(shù)和變量名使用 snake case 規(guī)范風(fēng)格。在 snake case 中,所有字母都是小寫并使用下劃線分隔單詞。這是一個包含函數(shù)定義示例的程序:
文件名: src/main.rs
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
我們在Rust 中通過輸入 ?fn
?后面跟著函數(shù)名和一對圓括號來定義函數(shù)。大括號告訴編譯器哪里是函數(shù)體的開始和結(jié)尾。
可以使用函數(shù)名后跟圓括號來調(diào)用我們定義過的任意函數(shù)。因?yàn)槌绦蛑幸讯x ?another_function
?函數(shù),所以可以在 ?main
?函數(shù)中調(diào)用它。注意,源碼中 ?another_function
?定義在 ?main
?函數(shù) 之后;也可以定義在之前。Rust 不關(guān)心函數(shù)定義所在的位置,只要函數(shù)被調(diào)用時出現(xiàn)在調(diào)用之處可見的作用域內(nèi)就行。
讓我們新建一個叫做 functions 的二進(jìn)制項(xiàng)目來進(jìn)一步探索函數(shù)。將上面的 ?another_function
?例子寫入 src/main.rs 中并運(yùn)行。你應(yīng)該會看到如下輸出:
$ 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!
Another function.
?main
?函數(shù)中的代碼會按順序執(zhí)行。首先,打印 “Hello, world!” 信息,然后調(diào)用 ?another_function
?函數(shù)并打印它的信息。
我們可以定義為擁有 參數(shù)(parameters)的函數(shù),參數(shù)是特殊變量,是函數(shù)簽名的一部分。當(dāng)函數(shù)擁有參數(shù)(形參)時,可以為這些參數(shù)提供具體的值(實(shí)參)。技術(shù)上講,這些具體值被稱為參數(shù)(arguments),但是在日常交流中,人們傾向于不區(qū)分使用 parameter 和 argument 來表示函數(shù)定義中的變量或調(diào)用函數(shù)時傳入的具體值。
在這版 ?another_function
?中,我們增加了一個參數(shù):
文件名: src/main.rs
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
嘗試運(yùn)行程序,將會輸出如下內(nèi)容:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/functions`
The value of x is: 5
?another_function
?的聲明中有一個命名為 ?x
?的參數(shù)。?x
?的類型被指定為 ?i32
?。當(dāng)我們將 ?5
?傳給 ?another_function
?時,?println!
? 宏會把 ?5
? 放在格式字符串中包含 ?x
? 的那對花括號的位置。
在函數(shù)簽名中,必須 聲明每個參數(shù)的類型。這是 Rust 設(shè)計(jì)中一個經(jīng)過慎重考慮的決定:要求在函數(shù)定義中提供類型注解,意味著編譯器再也不需要你在代碼的其他地方注明類型來指出你的意圖。而且,在知道函數(shù)需要什么類型后,編譯器就能夠給出更有用的錯誤消息。
當(dāng)定義多個參數(shù)時,使用逗號分隔,像這樣:
文件名: src/main.rs
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
這個例子創(chuàng)建了一個名為 ?print_labeled_measurement
?的函數(shù),它有兩個參數(shù)。第一個參數(shù)名為 ?value
?, 類型是 ?i32
?。第二個參數(shù)是 ?unit_label
?,類型是 ?char
?。然后,該函數(shù)打印包含 ?value
?和 ?unit_label
?的文本。
嘗試運(yùn)行代碼。使用上面的例子替換當(dāng)前 functions 項(xiàng)目的 src/main.rs 文件,并用 ?cargo run
? 運(yùn)行它:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/functions`
The measurement is: 5h
因?yàn)槲覀兪褂?nbsp;?5
?作為 ?value
?的值,?h
?作為 ?unit_label
?的值來調(diào)用函數(shù),所以程序輸出包含這些值。
函數(shù)體由一系列的語句和一個可選的結(jié)尾表達(dá)式構(gòu)成。目前為止,我們提到的函數(shù)還不包含結(jié)尾表達(dá)式,不過你已經(jīng)見過作為語句一部分的表達(dá)式。因?yàn)?Rust 是一門基于表達(dá)式(expression-based)的語言,這是一個需要理解的(不同于其他語言)重要區(qū)別。其他語言并沒有這樣的區(qū)別,所以讓我們看看語句與表達(dá)式有什么區(qū)別以及這些區(qū)別是如何影響函數(shù)體的。
語句(Statements)是執(zhí)行一些操作但不返回值的指令。表達(dá)式(Expressions)計(jì)算并產(chǎn)生一個值。讓我們看一些例子。
實(shí)際上,我們已經(jīng)使用過語句和表達(dá)式。使用 ?let
?關(guān)鍵字創(chuàng)建變量并綁定一個值是一個語句。在列表 3-1 中,?let y = 6;
? 是一個語句。
文件名: src/main.rs
fn main() {
let y = 6;
}
列表 3-1:包含一個語句的 ?main
?函數(shù)定義
函數(shù)定義也是語句,上面整個例子本身就是一個語句。
語句不返回值。因此,不能把 ?let
?語句賦值給另一個變量,比如下面的例子嘗試做的,會產(chǎn)生一個錯誤:
文件名: src/main.rs
fn main() {
let x = (let y = 6);
}
當(dāng)運(yùn)行這個程序時,會得到如下錯誤:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
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 2 previous errors; 1 warning emitted
?let y = 6
? 語句并不返回值,所以沒有可以綁定到 ?x
? 上的值。這與其他語言不同,例如 C 和 Ruby,它們的賦值語句會返回所賦的值。在這些語言中,可以這么寫 ?x = y = 6
?,這樣 ?x
?和 ?y
?的值都是 ?6
?;Rust 中不能這樣寫。
表達(dá)式會計(jì)算出一個值,并且你將編寫的大部分 Rust 代碼是由表達(dá)式組成的??紤]一個數(shù)學(xué)運(yùn)算,比如 ?5 + 6
?,這是一個表達(dá)式并計(jì)算出值 ?11
?。表達(dá)式可以是語句的一部分:在示例 3-1 中,語句 ?let y = 6;
? 中的 ?6
? 是一個表達(dá)式,它計(jì)算出的值是 ?6
?。函數(shù)調(diào)用是一個表達(dá)式。宏調(diào)用是一個表達(dá)式。用大括號創(chuàng)建的一個新的塊作用域也是一個表達(dá)式,例如:
文件名: src/main.rs
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {y}");
}
這個表達(dá)式:
{
let x = 3;
x + 1
}
是一個代碼塊,它的值是 ?4
?。這個值作為 ?let
?語句的一部分被綁定到 ?y
? 上。注意 ?x+1
? 這一行在結(jié)尾沒有分號,與你見過的大部分代碼行不同。表達(dá)式的結(jié)尾沒有分號。如果在表達(dá)式的結(jié)尾加上分號,它就變成了語句,而語句不會返回值。在接下來探索具有返回值的函數(shù)和表達(dá)式時要謹(jǐn)記這一點(diǎn)。
函數(shù)可以向調(diào)用它的代碼返回值。我們并不對返回值命名,但要在箭頭(?->
?)后聲明它的類型。在 Rust 中,函數(shù)的返回值等同于函數(shù)體最后一個表達(dá)式的值。使用 ?return
?關(guān)鍵字和指定值,可從函數(shù)中提前返回;但大部分函數(shù)隱式的返回最后的表達(dá)式。這是一個有返回值的函數(shù)的例子:
文件名: src/main.rs
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {x}");
}
在 ?five
?函數(shù)中沒有函數(shù)調(diào)用、宏、甚至沒有 ?let
?語句 —— 只有數(shù)字 ?5
?。這在 Rust 中是一個完全有效的函數(shù)。注意,也指定了函數(shù)返回值的類型,就是 ?-> i32
?。嘗試運(yùn)行代碼;輸出應(yīng)該看起來像這樣:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/functions`
The value of x is: 5
?five
?函數(shù)的返回值是 ?5
?,所以返回值類型是 ?i32
?。讓我們仔細(xì)檢查一下這段代碼。有兩個重要的部分:首先,?let x = five();
? 這一行表明我們使用函數(shù)的返回值初始化一個變量。因?yàn)?nbsp;?five
?函數(shù)返回 ?5
?,這一行與如下代碼相同:
let x = 5;
其次,?five
?函數(shù)沒有參數(shù)并定義了返回值類型,不過函數(shù)體只有單單一個 ?5
? 也沒有分號,因?yàn)檫@是一個表達(dá)式,我們想要返回它的值。
讓我們看看另一個例子:
文件名: src/main.rs
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1
}
運(yùn)行代碼會打印出 ?The value of x is: 6
?。但如果在包含 ?x + 1
? 的行尾加上一個分號,把它從表達(dá)式變成語句,我們將看到一個錯誤。
文件名: src/main.rs
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
運(yùn)行代碼會產(chǎn)生一個錯誤,如下:
$ 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: consider removing this semicolon
For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` due to previous error
主要的錯誤信息,“mismatched types”(類型不匹配),揭示了代碼的核心問題。函數(shù) ?plus_one
?的定義說明它要返回一個 ?i32
?類型的值,不過語句并不會返回值,使用單位類型 ?()
? 表示不返回值。因?yàn)椴环祷刂蹬c函數(shù)定義相矛盾,從而出現(xiàn)一個錯誤。在輸出中,Rust 提供了一條信息,可能有助于糾正這個錯誤:它建議刪除分號,這會修復(fù)這個錯誤。
更多建議: