Rust 函数
函数是组织代码的基本单位。Rust 的函数语法简单清晰,但有几个独特细节:用 fn 声明(不是 function 也不是 def)、参数必须显式标注类型、尾表达式即返回值(不加分号)、有"发散函数"这种特殊类型。这一篇我们把这些讲透。
1. 函数定义基础
Rust 用 fn 关键字定义函数。语法结构:
// 函数定义语法:
// fn 函数名(参数名: 类型, ...) -> 返回类型 { 函数体 }
fn add(a: i32, b: i32) -> i32 {
a + b // 尾表达式(无分号)= 返回值
}
fn greet() { // 不写 -> 表示返回 () (unit)
println!("你好");
}
fn main() {
let sum = add(3, 5);
println!("3 + 5 = {}", sum); // 8
greet(); // 你好
// 函数可以先使用后定义(Rust 不要求"先声明")
let r = multiply(4, 6);
println!("4 * 6 = {}", r); // 24
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}几个要点:
- fn 关键字:声明函数,不是 function、def、func。
- 参数必须标注类型:不像变量那样能推断。这是为了函数签名清晰(调用者一眼看到类型)。
- 返回类型用箭头:类似 TypeScript、Swift 的
-> T语法。 - 不写返回类型表示返回
()(unit),即"没有有意义的返回值"。 - 函数可以先使用后定义:Rust 不要求"先声明再使用",顺序自由。
2. 表达式函数体(关键)
这是 Rust 函数最重要的细节,上一篇语法已经讲过,这里再强调。函数体最后一行不加分号,就是返回值——这是 Rust 风格的写法:
// ===== 表达式函数体 vs 语句函数体 =====
// 推荐:用尾表达式返回值(无分号)
fn square(n: i32) -> i32 {
n * n // 无分号,作为返回值
}
// 也可以用 return 显式返回(加分号)
fn square2(n: i32) -> i32 {
return n * n; // 显式 return
}
// 早期返回必须用 return
fn abs(n: i32) -> i32 {
if n < 0 {
return -n; // 提前返回
}
n // 否则尾表达式返回
}
// 错误!尾表达式加了分号就变成语句,返回 () 而非 i32
// fn bad(n: i32) -> i32 {
// n + 1; // 编译错误!类型不匹配
// }规则总结:
- 尾表达式(无分号):作为函数返回值。简洁优雅,是 Rust 推荐风格。
- return 关键字:用于提前返回(条件分支里)。也用于普通返回,但不如尾表达式地道。
- 尾表达式加分号:变成语句,函数实际返回
()。如果你声明返回 i32,会编译错误。
这是 Rust 新手最常遇到的编译错误之一:声明了返回类型,但尾表达式加了分号,导致返回 () 不匹配。养成"返回值那行不加分号"的习惯。
3. 多返回值(用元组)
Rust 不像 Go 那样支持 func f() (int, error) 多返回值,但可以用元组达到同样效果。配合解构,代码很清晰:
// Rust 不支持多返回值,但可以返回元组
fn divmod(a: i32, b: i32) -> (i32, i32) {
(a / b, a % b)
}
fn main() {
let (quotient, remainder) = divmod(17, 5);
println!("17 / 5 = {} 余 {}", quotient, remainder); // 3 余 2
// 或者用 .0 .1 访问
let r = divmod(17, 5);
println!("{} {}", r.0, r.1);
// 需要返回多个不同类型的值?用元组或自定义结构体
let (name, age, active) = get_user();
println!("{} {} {}", name, age, active);
}
fn get_user() -> (String, u32, bool) {
("小明".to_string(), 20, true)
}这是 Rust 处理"多个返回值"的标准方式。当你需要返回更多结构化数据时,定义一个 struct 通常比大元组更清晰。
4. 发散函数(never 类型)
Rust 有一个独特的返回类型 !(读作 never / 发散),表示"这个函数永远不会正常返回"——总是 panic、无限循环,或终止进程:
// 发散函数(diverging function):返回类型是 !
// 表示"这个函数永远不会正常返回"(总是 panic 或无限循环)
fn always_panic() -> ! {
panic!("这个函数一定崩溃");
// 不会执行到这里
}
fn infinite_loop() -> ! {
loop {
// 永远不退出
}
}
// ! 类型可以转换为任何类型,因此很灵活
fn main() {
let x: i32 = match Some(5) {
Some(n) => n,
None => always_panic(), // ! 可以变成 i32
};
println!("{}", x);
}
// 常见的发散:process::exit、unreachable!、panic!、todo!()
fn not_done() -> i32 {
todo!("还没实现") // 调用时 panic,标注"待办"
}发散函数的好处是 ! 可以自动转换为任何类型。这让 match 等场景下类型仍然一致——比如 None 分支调用 panic! 后,编译器知道不会真的返回值,类型检查照样通过。
常用的发散函数:panic!(崩溃)、todo!(标注"待实现",调到就崩)、unreachable!(理论上不该到这)、标准库 process 模块的 exit 函数(退出进程)。
5. 参数传递与所有权(预告)
函数传参在 Rust 里有个特殊行为——默认是按值传递且转移所有权。这意味着传给函数后,原变量失效(对 String 等堆类型)。但通常我们用引用(借用)来避免所有权转移:
fn main() {
// 参数必须标注类型(不像 let 那样能推断)
let r = add(1, 2);
// 参数按值传递(默认 move 所有权,后面所有权篇详细讲)
let s = String::from("hi");
takes_string(s);
// println!("{}", s); // 编译错误!s 的所有权已转移
// 想保留所有权:传引用(借用)
let s2 = String::from("hi");
borrow_string(&s2); // 借用
println!("还能用:{}", s2); // OK
}
fn add(a: i32, b: i32) -> i32 { a + b }
fn takes_string(s: String) {
println!("拿到:{}", s);
} // s 离开作用域,内存释放
fn borrow_string(s: &String) {
println!("借用:{}", s);
} // s 是引用,不释放原数据这是 Rust 区别于所有语言的核心,后面所有权和借用两篇会深入展开。这里先记住:函数参数可以传值(转移所有权),也可以传引用(借用,不转移)。
6. 闭包(Closure)
闭包是 Rust 的匿名函数,语法很简洁。它能捕获外部变量,是函数式编程和迭代器的利器:
fn main() {
// 闭包:匿名函数,可以捕获外部变量
// 语法:|参数| { 体 } 或 |参数| 表达式
let add = |a, b| a + b; // 类型推断
println!("{}", add(1, 2)); // 3
// 捕获外部变量
let multiplier = 3;
let times = |x| x * multiplier; // 捕获了 multiplier
println!("{}", times(5)); // 15
// 多行闭包
let calc = |a, b| {
let sum = a + b;
sum * 2
};
println!("{}", calc(3, 4)); // 14
// 闭包常用于迭代器(下一篇会用)
let nums = vec![1, 2, 3, 4];
let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6, 8]
}闭包和普通函数的区别:
- 语法:用
|参数|而不是括号,体可以省略花括号(单表达式时)。 - 类型推断:闭包参数不用写类型(通常能推断),普通函数必须写。
- 捕获环境:闭包能用外层作用域的变量;普通函数不能。
- 用途:配合迭代器(map、filter、for_each 等)用得最多。
7. 函数命名约定
Rust 函数命名遵循蛇形命名法(snake_case):全小写,下划线分单词。如 calculate_total、read_to_string、is_empty。返回 bool 的函数习惯以 is_、has_、should_ 开头,如 is_empty()、contains()。
类型(结构体、枚举、Trait)用大驼峰:HttpClient、UserConfig。这种命名约定让代码可读性强,rustfmt 和 clippy 会帮你检查。
8. 函数指针与高阶函数
Rust 函数是"一等公民",可以作为参数、返回值传递。函数本身有类型 fn(T) -> U(小写 fn 是函数指针类型)。常见用法是配合闭包,写接受函数的"高阶函数":map、filter、fold 等迭代器方法都是高阶函数。这在实际项目中非常常见。
小结
这一篇你掌握了 Rust 函数的全部要点:fn 声明、参数类型标注、尾表达式即返回值(无分号)、用元组做多返回值、! 发散类型、闭包语法。最该记住的两个细节:尾表达式不加分号(加了变语句返回 ())、参数默认转移所有权(用引用避免)。下一篇我们进入 Rust 的灵魂——所有权。理解了所有权,你就理解了 Rust。
← 上一篇 Rust 控制流
下一篇 Rust 所有权(重点) →