函数

函数

每个 Rust 程序至少包含一个函数,也就是 main 函数:

 fn main() {
 }

这可能是最简单的函数声明。正如我们前面所提到的,fn 表明“这是一个函数”,紧随其后的是函数名字,一些括号,因为这个函数没有参数,然后一些花括号来表示函数体。这里有一个函数名称为 foo 的函数:

 fn foo() {
 }

那么,函数的带参数是什么样的呢?如下是打印一个数字的函数:

 fn print_number(x: i32) {
    println!("x is:{}", x);
 }

如下是使用 print_number 函数的完整程序:

 fn main() {
    print_number(5);
 }

 fn print_number(x: i32) {
    println!("x is: {}", x);
 }

正如你所看到的,函数参数的编写方式和 let 声明很相似:您可以在冒号之后添加参数的类型。

如下是一个将两个数加起来然后打印的完整程序:

fn main() {
    print_sum(5, 6);
}

fn print_sum(x: i32, y: i32) {
    println!("sum is: {}", x + y);
}

当你调用和声明函数的时候,都是利用逗号分隔参数。

不像 let,你必须声明函数参数的类型。如下的代码并不会正常工作:

 fn print_sum(x, y) {
    println!("sum is: {}", x + y);
 }

将会输出如下错误:

 expected one of `!`, `:`, or `@`, found `)`
fn print_number(x, y) {

这是一个深思熟虑的设计决策。尽管可以通过推理判断出数据类型,比如 Haskell 语言就拥有这种特性,但通常推荐显式声明是一种最佳实践。我们赞成强制函数声明类型推这种方式,尽管能够在函数内部能够推断出变量的类型,这种方式对于指出全文推断和支持推断来说都是是个不错方式。

返回值是什么样的呢?如下这个函数给整型参数进行加 1 操作:

fn add_one(x: i32) -> i32 {
    x + 1
}

Rust 函数仅仅只能返回一个值,并且在一个“箭头”符号之后声明返回值类型,该“箭头”由是一个破折号(-)和大于符号(>)组成。函数的最后一行决定它的返回值是什么。您会注意到这里函数最后一行缺少分号。如果我们给它加上分号:

fn add_one(x: i32) -> i32 {
    x + 1;
}

我们会得到一个错误:

error: not all control paths return a value
fn add_one(x: i32) -> i32 {
     x + 1;
}

help: consider removing this semicolon:
     x + 1;
          ^

这揭示了 Rust 两个有趣的事情:它是一门基于表达式的语言,并且分号与其他基于花括号和分号的语言中的分号是不同的。这两件事是相关的。

表达式 VS 语句

Rust 是一门主要基于表达式的语言。它只有两种类型的语句,其他的都是表达式。

那么有什么区别?表达式返回一个值,和但语句并不会。这也就是为什么我们以“并不是所有的控制路径返回一个值”结尾,在这里语句 x + 1;不返回一个值。Rust 中有两种类型的语句:“声明语句”和“表达式语句”。其他的都是一个表达式。让我们先谈谈声明语句。

在一些语言中,变量绑定可以写成表达式的形式,而不仅仅是语句。像 Ruby:

 x = y = 5

然而在 Rust 中使用 let 引起的绑定不是一个表达式。以下将会产生编译时错误:

 let x = (let y = 5); // expected identifier, found keyword 'let'

编译器告诉我们在这里它希望看到一个表达式的开始,而 let 只能声明一个语句,而不是一个表达式。

注意,给一个已经绑定变量(如 y = 5)赋值仍然是一个表达式,虽然它的值不是特别有用。不像其他语言,赋值语句的值为被赋值的值(例如前面例子中的 5),在 Rust 中赋值语句的值是一个空元组 ():

 let mut y = 5;
 let x = (y = 6); //x has the value '()', not '6'

Rust 中的第二种语句是表达式语句。它的目的是把任何表达式变成一个语句。实际上,Rust 的语法希望在语句之后还是语句。这意味着您可以使用分号来将表达式分隔开。这意味着 Rust 看起来很像大多数其他语言,需要你在每一行的末尾使用分号,而且你看到的所有 Rust 代码几乎都是以分号作为行结束符。

是什么例外让我们说“几乎”?其实你已经看到它,如下这段代码中:

fn add_one(x: i32) -> i32 {
    x + 1
}

函数声明返回一个 i32 类型的值,但如果语句末尾是分号,它将返回 ()。Rust 意识到这可能不是我们想要的,于是正如在前面我们看到错误中建议删除分号。

提前返回

如果出现提前返回呢?Rust 中的确有一个 return 关键字:

fn foo(x: i32) -> i32 {
    return x;

    // we never run this code!
    x + 1
}

利用 return 关键字仍然会将函数的最后一行返回,但是这被认为是糟糕的编程风格:

fn foo(x: i32) -> i32 {
    return x + 1;
}

如果你以往没有使用过基于表达式的语言,那么前面没有使用 return 的定义可能看起来有些奇怪,但是使用的时间长了就会变得很自然了。

发散函数

Rust 对于 ‘发散函数’有一些特殊的语法,该函数不返回任何值:

fn diverges() -> ! {
    panic!("This function never returns!");
}

panic! 是一个宏,和 println!() 类似,我们已经看见过。与 println!() 不同的是 panic!() 会导致当前执行线程的崩溃并给出特定的消息。

因为这个函数会导致崩溃,它永远不会返回,所以它有 “!” 类型,读“发散”。发散函数可以作为任何类型:

let x: i32 = diverges();
let x: String = diverges();