函数
开篇故事
想象你在组装乐高积木。每次需要搭建一个小房子时,你都要重新看说明书、找积木、一块块拼接——这既耗时又容易出错。但如果有一个"房子制作器"机器,你只需放入积木,按下按钮,房子就出来了!这就是函数的核心思想:将重复的逻辑封装起来,随时调用。
在 Rust 中,函数是代码的基本构建块。通过函数,你可以将复杂的问题分解为小的、可管理的部分,让代码更易读、可复用。
本章适合谁
如果你已经学完了变量和表达式,现在想学习如何组织代码、避免重复,本章适合你。函数是编程的基础,无论你是什么水平的开发者,都会频繁使用函数。
你会学到什么
完成本章后,你可以:
- 使用
fn关键字定义函数 - 理解参数和返回值的语法
- 区分表达式和语句
- 理解所有权的转移和借用
- 使用元组返回多个值
前置要求
学习本章前,你需要理解:
第一个例子
让我们看一个最简单的函数定义:
fn main() { let result = add(3, 5); println!("3 + 5 = {}", result); } fn add(a: i32, b: i32) -> i32 { a + b // 隐式返回 }
发生了什么?
第 6 行定义了 add 函数:
fn: 函数声明关键字add: 函数名称(a: i32, b: i32): 两个参数,类型都是i32-> i32: 返回值类型a + b: 函数体,没有分号表示返回值
第 2 行调用函数:add(3, 5) 返回 8。
Python/Java/C++ vs Rust 对比
如果你有其他语言经验,这个对比会帮助你快速理解:
| 概念 | Python | Java | C++ | Rust | 关键差异 |
|---|---|---|---|---|---|
| 函数定义 | def add(a, b): | int add(int a, int b) | int add(int a, int b) | fn add(a: i32, b: i32) -> i32 | Rust 必须标注参数类型 |
| 返回值 | return a + b | return a + b; | return a + b; | a + b (无分号) 或 return | Rust 支持隐式返回 |
| 多返回值 | return a, b | 需要类或数组 | 需要结构体或引用 | (a, b) 元组 | Rust 用元组原生支持 |
| 所有权传递 | 引用传递 | 引用传递 | 可选引用或值 | 默认移动,用 & 借用 | Rust 编译时检查所有权 |
| 默认参数 | def f(a=1): | 不支持 | 支持 | 不支持 | Rust 用关联函数代替 |
核心差异: Rust 在函数签名上最严格,参数必须标注类型,但用元组优雅支持多返回值。
原理解析
1. 函数定义语法
fn function_name(parameter1: Type1, parameter2: Type2) -> ReturnType {
// 函数体
expression // 返回值(无分号)
}
组成部分:
fn: 声明函数的关键字- 函数名:使用
snake_case命名(小写 + 下划线) - 参数:
name: Type格式,每个参数必须标注类型 - 返回值:
-> Type,如果无返回值可省略(相当于返回()) - 函数体:花括号包围的代码块
2. 参数与所有权
fn main() { let s = String::from("hello"); takes_ownership(s); // println!("{}", s); // ❌ 错误:s 已移动 let x = 5; makes_copy(x); println!("{}", x); // ✅ 可以:i32 被复制 } fn takes_ownership(some_string: String) { println!("{}", some_string); // some_string 在这里被丢弃 } fn makes_copy(some_integer: i32) { println!("{}", some_integer); // some_integer 是 Copy trait,离开作用域不丢弃 }
关键点:
- 传递所有权给函数:参数获得值的所有权
Copy类型(如i32):自动复制,原变量仍可用Drop类型(如String):所有权转移,原变量不可用
3. 返回值与隐式返回
// ✅ 隐式返回(推荐)
fn add(a: i32, b: i32) -> i32 {
a + b // 无分号
}
// ✅ 显式返回
fn add_explicit(a: i32, b: i32) -> i32 {
return a + b; // 有分号,使用 return 关键字
}
// ❌ 错误:有分号,返回 ()
fn add_wrong(a: i32, b: i32) -> i32 {
a + b; // 分号使这成为语句,返回 ()
}
规则:
- 最后一行表达式无分号 → 返回值
- 使用
return关键字 → 提前返回 - 有分号的表达式 → 语句,不返回值
4. 使用元组返回多个值
fn main() { let (sum, product) = calculate(3, 5); println!("和:{}, 积:{}", sum, product); } fn calculate(a: i32, b: i32) -> (i32, i32) { let sum = a + b; let product = a * b; (sum, product) // 返回元组 }
关键点:
- 返回类型:
(Type1, Type2) - 返回值:用括号包围多个值
- 解构:使用
let (a, b) = tuple获取各个值
5. 函数参数模式
#![allow(unused)] fn main() { // 不可变参数(默认) fn print_value(x: i32) { println!("{}", x); // x = x + 1; // ❌ 错误:不能修改 } // 可变参数 fn modify_value(mut x: i32) { x = x + 1; // ✅ 可以修改 println!("{}", x); } // 借用参数(不获取所有权) fn print_string(s: &String) { println!("{}", s); // s 仍归调用者所有 } // 忽略参数 fn unused_param(_x: i32) { println!("不使用 x"); } }
常见错误
错误 1: 忘记返回类型
// ❌ 错误:缺少返回类型
fn add(a: i32, b: i32) {
a + b
}
// ✅ 正确
fn add(a: i32, b: i32) -> i32 {
a + b
}
编译器输出:
error[E0308]: mismatched types
--> src/main.rs:4:5
|
1 | fn add(a: i32, b: i32) {
| - help: add a return type: `-> i32`
...
4 | a + b
| ^^^^^ expected `()`, found `i32`
错误 2: 返回值加分号
// ❌ 错误:返回值有分号
fn add(a: i32, b: i32) -> i32 {
a + b; // 分号使这成为语句
}
// ✅ 正确
fn add(a: i32, b: i32) -> i32 {
a + b // 无分号
}
编译器输出:
error[E0308]: mismatched types
--> src/main.rs:3:5
|
1 | fn add(a: i32, b: i32) -> i32 {
| --- expected `i32` because of return type
2 | a + b;
| - help: remove this semicolon to return this value
3 | }
| ^ expected `i32`, found `()`
错误 3: 所有权转移后使用
// ❌ 错误:所有权转移后使用变量
fn main() {
let s = String::from("hello");
print_string(s);
println!("{}", s); // ❌ s 已移动
}
fn print_string(s: String) {
println!("{}", s);
}
// ✅ 正确:使用借用
fn main() {
let s = String::from("hello");
print_string(&s);
println!("{}", s); // ✅ s 仍可用
}
fn print_string(s: &String) {
println!("{}", s);
}
错误 4: 参数类型不匹配
// ❌ 错误:类型不匹配
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let result = add(3.0, 5.0); // f64 不是 i32
}
// ✅ 正确:使用正确类型
fn main() {
let result = add(3, 5); // i32
}
动手练习
练习 1: 基础函数
定义一个函数 greet,接受名字参数并打印问候语:
// TODO: 定义 greet 函数
fn main() {
greet("Alice"); // 应打印 "Hello, Alice!"
greet("Bob"); // 应打印 "Hello, Bob!"
}
点击查看答案
fn greet(name: &str) { println!("Hello, {}!", name); } fn main() { greet("Alice"); greet("Bob"); }
解析: 使用 &str 作为参数类型,可以接受字符串字面量。
练习 2: 返回值
定义一个函数计算圆的面积:
// TODO: 定义 circle_area 函数,接受半径,返回面积
fn main() {
let area = circle_area(5.0);
println!("半径为 5 的圆面积:{}", area);
}
点击查看答案
fn circle_area(radius: f64) -> f64 { std::f64::consts::PI * radius * radius } fn main() { let area = circle_area(5.0); println!("半径为 5 的圆面积:{}", area); }
解析: 使用 f64 处理浮点数,PI 常量在 std::f64::consts 中。
练习 3: 多返回值
定义一个函数同时返回商和余数:
// TODO: 定义 div_mod 函数,返回 (商,余数)
fn main() {
let (quotient, remainder) = div_mod(17, 5);
println!("17 / 5 = {} 余 {}", quotient, remainder);
}
点击查看答案
fn div_mod(a: i32, b: i32) -> (i32, i32) { (a / b, a % b) } fn main() { let (quotient, remainder) = div_mod(17, 5); println!("17 / 5 = {} 余 {}", quotient, remainder); }
解析: 使用元组 (i32, i32) 返回两个值,/ 是除法,% 是取余。
练习 4: 所有权与借用
完成以下代码,使 s 在调用后仍可用:
fn main() {
let mut s = String::from("hello");
// TODO: 修改 print_and_add 函数,使 s 在调用后仍可用
print_and_add(&mut s);
println!("修改后:{}", s);
}
fn print_and_add(s: String) {
println!("{}", s);
// s.push_str(" world"); // 如果取消注释,需要可变借用
}
点击查看答案
fn main() { let mut s = String::from("hello"); print_and_add(&mut s); println!("修改后:{}", s); } fn print_and_add(s: &mut String) { println!("{}", s); s.push_str(" world"); }
解析: 使用 &mut String 可变借用,函数可以修改但不获取所有权。
故障排查
Q: 什么时候使用 return 关键字?
A: 通常不需要。Rust 函数隐式返回最后一个表达式。仅在以下情况使用:
- 提前返回(在
if语句中) - 从深层嵌套中返回
- 使代码更清晰
Q: 参数类型应该用 &str 还是 &String?
A: 优先使用 &str,因为:
- 可以接受
&String和字符串字面量 - 更灵活、更通用
- 除非需要
String的方法,否则不需要&String
Q: 函数可以返回引用吗?
A: 可以,但需要生命周期标注:
#![allow(unused)] fn main() { fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } }
Q: 如何定义不接受参数的函数?
A: 使用空括号 ():
#![allow(unused)] fn main() { fn say_hello() { println!("Hello!"); } }
知识扩展 (选学)
函数指针
函数也可以作为参数传递:
fn add(a: i32, b: i32) -> i32 { a + b } fn calculate<F>(a: i32, b: i32, operation: F) -> i32 where F: Fn(i32, i32) -> i32, { operation(a, b) } fn main() { let result = calculate(3, 5, add); println!("结果:{}", result); }
泛型函数
函数可以接受泛型参数:
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
闭包简介
闭包是匿名函数,可以捕获环境变量:
#![allow(unused)] fn main() { let x = 5; let add_x = |y| y + x; println!("{}", add_x(3)); // 打印 8 }
小结
核心要点:
- 函数定义:
fn name(params) -> ReturnType { body } - 参数类型: 必须标注类型,使用
name: Type格式 - 返回值: 最后一个表达式(无分号)或使用
return - 所有权: 参数默认获取所有权,使用
&借用 - 元组返回: 使用
(a, b)返回多个值
关键术语:
- Function: 函数,可重用的代码块
- Parameter: 参数,函数的输入
- Return Type: 返回类型,函数的输出类型
- Ownership: 所有权,值的归属
- Borrowing: 借用,临时使用值
下一步:
术语表
| English | 中文 |
|---|---|
| Function | 函数 |
| Parameter | 参数 |
| Argument | 实参 |
| Return | 返回 |
| Ownership | 所有权 |
| Borrow | 借用 |
| Tuple | 元组 |
完整示例:src/basic/expression_sample.rs - 函数定义和调用
相关示例:src/basic/generic_sample.rs - 泛型函数示例
知识检查
快速测验(答案在下方):
- 这段代码能编译通过吗?为什么?
fn add(a: i32, b: i32) -> i32 {
a + b;
}
-
函数参数默认是可变还是不可变?
-
如何返回多个值?
点击查看答案与解析
- ❌ 不能 - 返回值有分号,返回
()而不是i32 - 不可变 - 需要使用
mut关键字 - 使用元组:
fn foo() -> (i32, String) { (1, "hello".to_string()) }
关键理解: Rust 函数返回值是最后一个表达式(无分号)。
继续学习
💡 记住:函数是代码的基石。好的函数应该短小、专注、可复用!