Rust 函数
函数
函数在 Rust 代码中非常常见。你已经见过了语言中最重要的函数之一:main
函数,它是许多程序的入口点。你还见过 fn 关键字,它允许你声明新的函数。
Rust 代码采用蛇形命名法(snake case)作为函数和变量名的惯用风格,即所有字母小写,单词之间用下划线分隔。下面是一个包含函数定义示例的程序:
文件名:src/main.rs
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
我们通过输入 fn
后跟函数名和一对括号来定义 Rust 中的函数。花括号告诉编译器函数体的开始和结束位置。
我们可以通过输入函数名和一对括号来调用任何已定义的函数。因为 another_function
已在程序中定义,所以可以在 main
函数内部调用它。注意,我们在源代码中将 another_function
定义在 main
函数之后;也可以在之前定义。Rust 不关心你在哪里定义函数,只要它们在调用者可见的作用域中定义即可。
让我们新建一个名为 functions
的二进制项目,进一步探索函数。将 another_function
示例放入 src/main.rs 并运行。你应该会看到如下输出:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/functions`
Hello, world!
Another function.
这些行按照它们在 main
函数中出现的顺序执行。首先打印 “Hello, world!” 消息,然后调用 another_function 并打印其消息。
参数
我们可以定义带有参数的函数,参数是函数签名的一部分的特殊变量。当函数有参数时,你可以为这些参数提供具体的值。严格来说,这些具体的值称为实参(arguments),但在日常交流中,人们往往将参数(parameter)和实参(argument)混用,既指函数定义中的变量,也指调用函数时传入的具体值。
在这个版本的 another_function 中,我们添加了一个参数:
文件名:src/main.rs
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
尝试运行这个程序,你应该会得到如下输出:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/functions`
The value of x is: 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!("The measurement is: {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` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/functions`
The measurement is: 5h
因为我们调用函数时,value
的值为 5,unit_label
的值为 ‘h’,所以程序输出包含了这些值。
语句与表达式
函数体由一系列语句组成,最后可以以一个表达式结尾。到目前为止,我们介绍的函数还没有包含结尾的表达式,但你已经见过语句中的表达式。由于 Rust 是一门基于表达式的语言,这一区别很重要。其他语言没有这样的区分,所以让我们看看语句和表达式是什么,以及它们的区别如何影响函数体。
语句是执行某些操作但不返回值的指令。
表达式会计算并产生一个值。让我们看一些例子。
实际上,我们已经用过语句和表达式。用 let
关键字创建变量并赋值是一个语句。在示例 3-1 中,let y = 6
; 是一个语句。
文件名:src/main.rs
fn main() {
let y = 6;
}
示例 3-1:包含一个语句的 main
函数声明
函数定义也是语句;整个前面的例子本身就是一个语句。(如下面所示,调用函数不是语句。)
语句不返回值。因此,你不能将 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);
| ^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
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;
|
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` (bin "functions") due to 1 previous error; 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 y = {
let x = 3;
x + 1
};
println!("The value of y is: {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!("The value of x is: {x}");
}
在 five
函数中没有函数调用、宏调用,甚至没有 let
语句——只有一个单独的数字 5
。这在 Rust 中是完全合法的函数。注意函数的返回类型也被指定为 -> i32
。试着运行这段代码,输出应该如下:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/functions`
The value of x is: 5
five
函数中的 5
就是函数的返回值,因此返回类型为 i32
。让我们更详细地分析一下。有两个重要点:首先,let x = five()
; 这一行表明我们用函数的返回值初始化变量。因为 five 返回 5
,这一行等价于:
let x = 5;
其次,five
函数没有参数,并定义了返回值的类型,但函数体只有一个没有分号的 5,因为它是我们想要返回值的表达式。
让我们再看一个例子:
文件名: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
}
运行这段代码会打印 The value of x is: 6
。但如果我们在 x + 1
这一行末尾加上分号,把它从表达式变成语句,就会报错:
文件名: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;
}
编译这段代码会产生如下错误:
$ 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: remove this semicolon to return this value
For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` (bin "functions") due to 1 previous error
主要的错误信息 mismatched types
揭示了代码的核心问题。plus_one
函数的定义声明它会返回 i32
,但语句不会计算出值,表达为 ()
,即单元类型。因此没有返回任何值,这与函数定义矛盾,导致报错。在输出中,Rust 提供了可能的修复建议:移除分号,这样就能修复错误。
代码示例
fn main() {
println!("Hello, world!");
// another_function();
another_function(5);
print_labeled_measurement(5, 'h');
println!("The value of function give_me_five is: {}", give_me_five());
let x = plus_one(5);
println!("The value of x is: {x}");
}
// fn another_function() {
// println!("Another function.");
// }
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
fn give_me_five() -> i32 {
5
}
fn plus_one(x: i32) -> i32 {
x + 1
}
运行结果:
Hello, world!
The value of x is: 5
The measurement is: 5h
The value of function give_me_five is: 5
The value of x is: 6