函数详解

前言

任何一门编程语言几乎都脱离不了:变量、基本类型、函数、注释、循环、条件判断,这是一门编程语言的语法基础,只有当掌握这些基础语法及概念才能更好的学习 Rust。

函数

在 Rust 中,函数是基本的执行单元,本篇将介绍 Rust 中的函数,包括其定义、参数、返回值以及一些注意事项。

函数定义与调用

  • Rust 中的函数使用fn关键字定义,后跟函数名称和一对圆括号。
  • 在圆括号中,可以定义零个或多个参数,每个参数后面跟着一个冒号(:)和它的类型。
  • 函数体用大括号({})包围。
  • 如果函数需要返回值,必须声明返回类型,通过在参数列表后添加一个箭头(->)和返回类型来完成。
1
2
3
4
fn <方法名>(<参数名1>: <类型>, <参数名2>: <类型2>) -> <返回值类型> {
// 函数体,如果有返回值,则使用return关键字,或者省略它,
// 最后的表达式作为返回值(无分号)
}

示例: 函数 main 是程序的入口,它会调用 another_function

1
2
3
4
5
6
7
8
fn main() {
println!("Hello, world!");
another_function();
}

fn another_function() {
println!("另一个函数.");
}

参数

函数可以接收零个或者接受多个参数,并为参数指定类型:

1
2
3
4
5
6
7
fn print_sum(a: i32, b: i32) {
println!("和是: {}", a + b);
}

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

在 Rust 中,函数参数是不可变的,需要显式使用mut关键字标识:

1
2
3
4
5
6
7
8
9
10
11
12
fn change_value(x: i32) {
// 此时 x 会报错,因为 x 没有声明是可变的
// error[E0384]: cannot assign to immutable argument `x`
x += 1;
println!("x in function: {}", x);
}

fn main() {
let mut x = 5;
change_value(x);
println!("x after function: {}", x);
}

使用mut关键字使参数可变:

1
2
3
4
5
6
7
8
9
10
11
fn change_value(mut x: i32) {
// 这里 x 被成功修改
x += 1;
println!("x in function: {}", x);
}

fn main() {
let mut x = 5;
change_value(x);
println!("x after function: {}", x);
}

返回值

在 Rust 函数中,返回值的类型紧跟在参数列表之后,并以 -> 开头,返回值通过在函数的最后一个表达式上省略分号来隐式返回的,这将变为函数的返回值。

注意: 最后一个表达式没有分号才作为返回值,如果有分号将作为一个表达式。

1
2
3
fn add(a: i32, b: i32) -> i32 {
a + b // 注意这里没有分号,表示这是返回的值
}

在 Rust 中,返回值与最后一个表达式的值有关,并不需要显式的 return 关键字,如果确实需要提前返回,也可以像其他语言那样使用 return 关键字。

1
2
3
4
5
6
fn add(a: i32, b: i32) -> i32 {
if a == 0 || b == 0 {
return 0 // 通过 return 提前返回
}
a + b
}

私有性

在 Rust 中,默认情况下函数是私有的,只能在定义它们的模块内部访问。与其他语言不同的是,Rust 没有受保护(protected)、私有的(private) 关键字,只有 pub 关键字声明是公共的,当函数使用pub关键字后,才能使函数在其他模块中可见。

1
2
3
4
5
6
7
8
9
// 默认是私有的,只能在模块内访问
fn add(a: i32, b: i32) -> i32 {
a + b
}

// 使用 pub 关键字声明公共的
pub fn add_two_num(a: i32, b: i32) -> i32 {
a + b
}

可变参数

Rust 不支持直接的可变参数函数(Variadic Functions),但可以通过接受一个切片或者元组来间接实现。

数组可变参数示例

1
2
3
4
5
6
7
8
9
fn sum(slice: &[i32]) -> i32 {
slice.iter().fold(0, |acc, &x| acc + x)
}

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let result = sum(&numbers);
println!("The sum is {}", result);
}

在这个例子中,sum函数接受一个i32类型的切片作为参数,并使用迭代器的fold方法来计算总和。然后就可以通过一个向量调用这个函数,在这种情况下是numbers向量。将向量的借用&numbers传递给sum函数时,它将被自动借用为一个切片,这样函数就可以接受不同数量的参数。

元组可变参数示例

1
2
3
4
5
6
7
8
9
fn sum_tuple(args: (i32, i32, i32)) -> i32 {
let (a, b, c) = args;
a + b + c
}

fn main() {
let result = sum_tuple((1, 2, 3));
println!("The sum is {}", result);
}

在这个例子中,sum_tuple函数接受一个包含三个i32元素的元组。元组在参数列表中是固定大小的,但是可以通过创建不同大小的元组类型来模拟可变参数。但这种方法不如使用切片那样灵活,如果需要不同数量的参数,通常推荐使用切片。

模式匹配 / 参数解构

在 Rust 中,函数参数的行为就像let绑定一样,这意味着可以在函数定义中直接解构复合类型。例如,倘若你传递一个元组或结构体给函数,可以直接在参数列表中解构它们,从而得到它们的部分或全部内容。

这就像是在let语句中进行模式匹配,而函数参数就是所匹配的模式。这种特性在处理具有多个部分的数据类型时非常有用,我们无须先创建一个变量,然后再使用模式匹配来解构它,因为我们可以直接在函数的参数中完成这个步骤。

示例:

1
2
3
4
5
6
7
8
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}

fn main() {
let point = (3, 5);
print_coordinates(&point);
}

在这个例子中,print_coordinates 函数接受一个引用到一个元组 (i32, i32) 的参数。参数 &(x, y) 是一个模式,它解构传入的元组,并允许我们在函数体内直接使用 xy。当我们调用 print_coordinates(&point) 时,point 元组被解构,元组中的值被分别绑定到 xy

同样的解构也可以被用在 let 绑定中,例如:

1
let (a, b) = (1, 2);

在这里,元组 (1, 2) 被解构,并且值 12 分别绑定到变量 ab

因此,Rust中的参数解构提供了一种非常强大和表达性的方式来处理输入参数,无需手动解构就可以直接取用复合型数据的一部分。

注意事项

rust 中的函数与其他语言有一些差异和需要注意的地方:

  • 函数和变量名采用 snake_case 的命名方式。
  • Rust 中的函数是表达式,这代表你可以在更大的表达式中嵌套使用它们。
  • Rust 不允许函数重载,即在同一作用域中不能有多个同名函数,即使参数类型不同也不行。
  • 没有默认参数值或参数名,不能像某些语言那样只提供某些参数。
  • 递归函数,即调用自身的函数,在 Rust 中是被允许的,但是需要注意栈溢出的风险。

结语

这是 Rust 基础语法的最后一篇,后面将开始讲解 Rust 所有权、借用等核心基础概念,但这块涉及比较多,加上新年开工比较忙,过段时间在更新相关内容。


函数详解
https://blog.pangcy.cn/2024/02/18/后端编程相关/rust/rust 基础/函数详解/
作者
子洋
发布于
2024年2月18日
许可协议