🦀 Rust 函数通俗入门
📘 Rust 是一门语法精炼但设计严谨的系统级语言。本文围绕函数这一主线,带你真正搞懂 Rust 最关键的语法思想,包括表达式驱动、闭包捕获、Trait 限制、生命周期标注与所有权规则,每遇到一个新概念,就在函数中通俗解释清楚。
📑 目录
- 函数基础语法:println!、format! 到底是什么?
- 什么是表达式驱动语言?Rust 函数为什么不需要
return
? - 函数参数传递方式:传值、借用、可变借用和所有权规则
- 所有权是什么?变量"不能再用"是怎么回事?
- 什么是胖类型?为什么
String
和Vec
会被 move? - 闭包彻底讲解:
||
是什么?为什么有 Fn、FnMut、FnOnce? - 泛型函数:
<T>
是什么?什么是 trait 限制? - Trait 是"类型的能力"?PartialOrd 和 Display 是什么?
- 隐式调用 trait 方法机制:println! 怎么就能打印我们自定义的类型?
- 生命周期:什么时候要加
'a
?怎么写、怎么理解? - 总结:写函数前你该先想清楚的三件事
1️⃣ 函数基础语法:println!、format! 到底是什么?
我们从一个简单函数开始:
rust
fn greet(name: &str) -> String {
format!("Hi, {}!", name)
}
这个函数接收一个字符串引用 &str
,返回一个堆上的字符串(String
)。
你会发现:
- 结尾没有
return
,却能返回值 format!
的末尾有!
,而不是括号
这就是 Rust 的第一个"神奇"点。
❗ println!
和 format!
为什么有 !
?
它们不是普通函数,而是宏(macro)。宏在编译期间会被"展开",是代码生成器。
println!
是打印宏format!
是字符串格式构造宏- Rust 规定宏名后面必须加
!
,和函数区分开
rust
let name = "Tom";
let msg = format!("Hello, {}!", name); // 构造字符串
println!("{}", msg); // 打印字符串
2️⃣ 什么是表达式驱动语言?为什么函数不用写 return
?
Rust 是"表达式驱动"的语言,也就是说:
你写的很多代码块不仅是"做事",而是"有值"。
表达式(expression):执行后有返回值
语句(statement):只是执行,不返回值
rust
let x = 5 + 3; // 表达式
let y = if x > 5 { "big" } else { "small" }; // if 也是表达式
函数体也是表达式:
rust
fn add(a: i32, b: i32) -> i32 {
a + b // 最后一行表达式就是返回值
}
📌 如果你加了分号 a + b;
,就成了语句,没法返回。
3️⃣ 函数参数传递方式:传值、借用、可变借用
✅ 传值(move)
rust
fn take(val: String) {
println!("{}", val);
}
let name = String::from("Tom");
take(name);
// println!("{}", name); // ❌ name 已被 move,不能再用
Rust 默认是"所有权转移",函数接收变量后,调用者就不能再用了。
✅ 借用(&)
rust
fn show(val: &String) {
println!("{}", val);
}
let s = String::from("Hi");
show(&s);
println!("{}", s); // ✅ 仍然可用
引用就是借用,意味着函数"只看一下",不拿走。
✅ 可变借用(&mut)
rust
fn append(val: &mut String) {
val.push_str("!!!");
}
let mut msg = String::from("Hello");
append(&mut msg);
println!("{}", msg); // Hello!!!
Rust 的引用规则:
- ✅ 多个不可变引用可以并存
- ✅ 只能有一个可变引用
- ❌ 不可变和可变不能同时存在
📦 类比:一本书可以被多人看(不可变引用),但只能一个人做笔记(可变引用)。
4️⃣ 所有权是什么?变量不能用了是怎么回事?
Rust 用"所有权"来管理内存,而不是垃圾回收。
rust
fn consume(s: String) {
println!("{}", s);
}
let x = String::from("Hi");
consume(x);
// println!("{}", x); // ❌ 错:x 的所有权已转移
📦 类比:你把手机送人了,对方拿走,你就不能再用。
5️⃣ 什么是胖类型?为什么 String 和 Vec 会被 move?
Rust 将类型分为两类:
类型 | 是否 Copy | 示例 |
---|---|---|
轻量类型 | ✅ Copy | i32 , bool , char |
胖类型 | ❌ Move | String , Vec , Box , HashMap |
胖类型的特点:
- 有堆内存(要手动释放)
- 拷贝开销大,容易造成内存泄漏
- 默认只能
move
6️⃣ 闭包彻底讲解:||
是什么?为什么有 Fn、FnMut、FnOnce?
✅ 闭包基本写法
rust
let square = |x: i32| x * x;
println!("{}", square(4)); // 16
语法说明:
|x|
是参数- 函数体是
x * x
- 没有
fn
,没有名字,随写随用
✅ 把闭包传给函数
rust
fn apply<F>(f: F)
where
F: Fn(i32) -> i32,
{
println!("{}", f(5));
}
apply(|x| x + 1); // 输出 6
✅ 闭包的三种类型:Fn / FnMut / FnOnce
Rust 根据闭包"如何捕获变量"自动分配三种类型:
类型 | 捕获方式 | 能否调用多次 | 场景 |
---|---|---|---|
Fn |
借用(只读) | ✅ 多次 | 打印 |
FnMut |
可变借用 | ✅ 多次 | 修改 |
FnOnce |
所有权转移(move) | ❌ 一次 | 消耗 |
✅ Fn 示例:
rust
let msg = String::from("hi");
let say = || println!("{}", msg); // 只读
say(); say(); // 可以多次调用
✅ FnMut 示例:
rust
let mut count = 0;
let mut counter = || { count += 1; println!("{}", count); };
counter(); counter(); // 修改 count
✅ FnOnce 示例:
rust
let name = String::from("Tom");
let consume = move || println!("{}", name); // move 进闭包
consume();
// consume(); // ❌ name 被拿走,闭包不能再调用
7️⃣ 泛型函数:<T>
是什么?什么是 trait 限制?
rust
fn max<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
解释:
T
是泛型:可以是任意类型T: PartialOrd
是 Trait 限制:表示 T 必须能比较大小
8️⃣ Trait 是"类型的能力"?PartialOrd 和 Display 是什么?
Trait 就像"接口",表示一个类型具备某种能力。
rust
fn show<T: std::fmt::Display>(val: T) {
println!("{}", val);
}
✅ 为类型实现 Display:
rust
struct Dog;
impl std::fmt::Display for Dog {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Woof!")
}
}
println!("{}", Dog); // 输出 Woof!
📦 类比:你告诉 Rust:"我这个类型怎么被打印",Rust 自动替你处理。
9️⃣ 隐式调用 trait 方法机制
你明明没写 .fmt()
或 .to_string()
,为啥 println!("{}", dog);
能打印?
这是 Rust 的"隐式 trait 方法调用"机制:
- 编译器看到
{}
就会查找Display
实现 - 找到了就自动调用
fmt()
- 所以你不需要手动调用
.fmt()
,Rust 自动代劳
📦 类比:你下命令"打印",助理会自动选打印方式。
🔟 生命周期:什么时候要加 'a
?
Rust 的规则是:返回引用时必须声明生命周期。
rust
fn first<'a>(s: &'a str) -> &'a str {
&s[..1]
}
解释:
'a
是生命周期参数- 表示"返回值的引用"和"输入引用"活得一样久
- 防止你返回了"已经销毁的值"
✅ 总结:写函数时你要先想这三件事
问题 | 要考虑的点 |
---|---|
参数传递方式 | move 还是借用?是否可变? |
是否用泛型 | 如果是通用函数,需要加 <T> 和 trait 限制 |
返回引用了吗? | 如果返回引用,要写生命周期 'a |
Rust 的函数不仅仅是"能执行的块",它是所有权、表达式、类型安全、行为接口等系统的交汇点。理解函数的全过程,也就理解了 Rust 的核心编程方式。