功能


函数在 Rust 代码中很普遍。您已经看到了该语言中最重要的函数之一: main 函数,它是许多程序的入口点。您还看到了 fn 关键字,它允许您声明新函数。


Rust 代码使用蛇形大小写作为函数和变量名称的常规样式,其中所有字母都是小写的,并在单独的单词下划线。下面是一个包含示例函数定义的程序:


文件名: src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}


我们在 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` profile [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.


这些行按照它们在 main 函数中的显示顺序执行。首先打印 “Hello, world!” 消息,然后调用 another_function 并打印其消息。


参数


我们可以将函数定义为具有参数,参数是作为函数签名一部分的特殊变量。当函数具有参数时,您可以为其提供这些参数的具体值。从技术上讲,具体值称为参数,但在随意交谈中,人们倾向于交替使用参数参数这两个词来表示函数定义中的变量或调用函数时传入的具体值。


在这个版本的 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!macro 将 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。然后,该函数将打印包含unit_label的文本。


让我们尝试运行这段代码。替换当前函数中的程序 项目的 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 是一种基于表达式的语言,所以这是一个需要理解的重要区别。其他语言没有相同的区别,所以让我们看看什么是语句和表达式,以及它们的区别如何影响函数体。


  • 语句是执行某些作但不返回值的指令。

  • 表达式的计算结果为结果值。让我们看一些例子。


我们实际上已经使用了 statements 和 expressions。使用 let 关键字创建变量并为其赋值是一个语句。在示例 3-1 中, 设 y = 6;是一个声明。


文件名: src/main.rs

fn main() { 设 y = 6; }

示例 3-1:包含一个语句的 main 函数声明


函数定义也是语句;前面的整个示例本身就是一个语句。(正如我们将在下面看到的,调用函数不是 statement.)


语句不返回值。因此,您不能将 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,并且 xy 都具有值 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}");
}


此表达式:


{ 设 x = 3; x + 1 }


是一个块,在本例中,其计算结果为 4。该值绑定到 y 作为 let 语句的一部分。请注意,x + 1 行的末尾没有分号,这与你目前看到的大多数行不同。表达式不包括结束分号。如果在表达式的末尾添加分号,则会将其转换为语句,并且不会返回值。在接下来探索函数返回值和表达式时,请记住这一点。


具有返回值的函数


函数可以将值返回给调用它们的代码。我们不命名返回值,但必须在箭头 (->) 后声明它们的类型。在 Rust 中,函数的返回值与函数体块中 final 表达式的值同义。您可以通过使用 return 关键字并指定值从函数提前返回,但大多数函数都隐式返回最后一个表达式。下面是一个返回值的函数示例:


文件名: src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}


5 个函数中没有函数调用、宏,甚至没有 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


分之是函数的返回值,这就是为什么返回类型为 i32 的原因。让我们更详细地研究一下。有两个重要的部分:首先,line let x = five();表明我们正在使用函数的返回值来初始化一个变量。由于函数 five 返回 5,因此该行与以下内容相同:

#![allow(unused)]
fn main() {
let x = 5;
}


其次,5 函数没有参数并定义返回值的类型,但函数的主体是一个没有分号的孤独 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
}


运行这段代码会打印 x 的值为: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 的 ID 值,但语句的计算结果不是 () 值,该值由 () 表示,即 Unit 类型。因此,不会返回任何内容,这与函数定义相矛盾并导致错误。在此输出中,Rust 提供了一条消息,可能有助于纠正此问题:它建议删除分号,这将修复错误。