下面把 Rust 里的 & 当成一个核心符号来系统讲。可以先记一句话:
& 的主要作用是“借用”,也就是拿到某个值的引用,但不取得它的所有权。
1. & 最常见含义:创建引用
fn main() {
let x = 10;
let r = &x;
println!("x = {}", x);
println!("r = {}", r);
}
这里:
let r = &x;
意思是:
r是对x的引用,r借用了x,但不拥有x。
可以理解为:
x: i32 r: &i32
也就是说,x 是真正的数据,r 是指向这个数据的引用。
2. &T 是不可变引用
fn main() {
let name = String::from("张三");
let r = &name;
println!("{}", r);
println!("{}", name);
}
这里 r 的类型是:
&String
它是一个不可变引用,只能读取,不能修改。
下面这样不行:
fn main() {
let name = String::from("张三");
let r = &name;
r.push_str("你好"); // 错误
}
因为 r 是 &String,不是 &mut String。
3. &mut T 是可变引用
如果想通过引用修改原值,需要使用:
&mut
例子:
fn main() {
let mut name = String::from("张三");
let r = &mut name;
r.push_str("你好");
println!("{}", name);
}
这里:
let r = &mut name;
表示:
可变借用
name。
r 的类型是:
&mut String
注意:原变量本身也必须是 mut。
下面这样不行:
fn main() {
let name = String::from("张三");
let r = &mut name; // 错误,name 不是 mut
}
应该写成:
let mut name = String::from("张三");
let r = &mut name;
4. 为什么需要 &?避免所有权转移
Rust 的所有权规则决定了,很多时候如果不使用 &,值会被移动。
例如:
fn print_name(name: String) {
println!("{}", name);
}
fn main() {
let name = String::from("张三");
print_name(name);
println!("{}", name); // 错误
}
因为:
print_name(name);
把 name 的所有权移动进了函数。
函数执行完后,原来的 name 就不能再用了。
改成引用:
fn print_name(name: &String) {
println!("{}", name);
}
fn main() {
let name = String::from("张三");
print_name(&name);
println!("{}", name); // 可以继续使用
}
这里:
print_name(&name);
表示:
把
name借给函数用一下,不把所有权交出去。
5. 函数参数中经常用 &
场景一:只读,不修改
fn show(s: &String) {
println!("{}", s);
}
fn main() {
let text = String::from("hello");
show(&text);
println!("{}", text);
}
这适合函数只需要读取数据的场景。
不过在 Rust 中,字符串参数通常更推荐写成:
fn show(s: &str) {
println!("{}", s);
}
fn main() {
let text = String::from("hello");
show(&text);
show("world");
}
原因是 &str 更通用,既可以接收 String 的引用,也可以接收字符串字面量。
场景二:需要修改外部变量
fn add_suffix(s: &mut String) {
s.push_str(" world");
}
fn main() {
let mut text = String::from("hello");
add_suffix(&mut text);
println!("{}", text);
}
这里函数参数:
s: &mut String
表示函数可以修改外面的 String。
调用时也必须写:
add_suffix(&mut text);
6. & 和 * 的关系:引用和解引用
& 是取引用,* 是解引用。
fn main() {
let x = 10;
let r = &x;
println!("{}", r);
println!("{}", *r);
}
这里:
let r = &x;
是取引用。
*r
是通过引用拿到里面的值。
对于普通数字:
let x = 10; let r = &x;
可以理解为:
r 指向 x *r 取出 r 指向的值
修改可变引用时,经常需要用 *:
fn main() {
let mut x = 10;
let r = &mut x;
*r += 5;
println!("{}", x);
}
输出:
15
7. Rust 引用的两条核心规则
Rust 的引用规则非常重要。
规则一:可以有多个不可变引用
fn main() {
let x = 10;
let r1 = &x;
let r2 = &x;
let r3 = &x;
println!("{}, {}, {}", r1, r2, r3);
}
这个可以。
因为大家都只是读,不修改。
规则二:同一时间只能有一个可变引用
fn main() {
let mut x = 10;
let r1 = &mut x;
let r2 = &mut x; // 错误
println!("{}, {}", r1, r2);
}
Rust 不允许同时存在两个可变引用。
原因是防止数据竞争和混乱修改。
规则三:不可变引用和可变引用不能同时存在
fn main() {
let mut x = 10;
let r1 = &x;
let r2 = &mut x; // 错误
println!("{}, {}", r1, r2);
}
因为 r1 正在读,r2 又想改,Rust 不允许这种情况。
不过下面是可以的:
fn main() {
let mut x = 10;
let r1 = &x;
println!("{}", r1);
let r2 = &mut x;
*r2 += 1;
println!("{}", r2);
}
因为 r1 最后一次使用在 println!("{}", r1);,之后它的借用就结束了。
8. & 常用于遍历集合
遍历 Vec,但不拿走元素所有权
fn main() {
let names = vec![
String::from("张三"),
String::from("李四"),
String::from("王五"),
];
for name in &names {
println!("{}", name);
}
println!("{:?}", names);
}
这里:
for name in &names
表示借用整个 Vec,循环里的 name 类型是:
&String
不会把 Vec 里面的元素移动走。
如果不加 &:
fn main() {
let names = vec![
String::from("张三"),
String::from("李四"),
String::from("王五"),
];
for name in names {
println!("{}", name);
}
println!("{:?}", names); // 错误
}
因为:
for name in names
会把 names 里的元素移动出来,循环后 names 不能继续使用。
9. &mut 常用于可变遍历
fn main() {
let mut nums = vec![1, 2, 3];
for n in &mut nums {
*n *= 10;
}
println!("{:?}", nums);
}
输出:
[10, 20, 30]
这里:
for n in &mut nums
循环里的 n 类型是:
&mut i32
所以要修改值,需要:
*n *= 10;
10. &[T]:切片引用
& 也经常和切片一起用。
fn sum(nums: &[i32]) -> i32 {
let mut total = 0;
for n in nums {
total += n;
}
total
}
fn main() {
let arr = [1, 2, 3, 4, 5];
let vec = vec![10, 20, 30];
println!("{}", sum(&arr));
println!("{}", sum(&vec));
}
这里函数参数是:
nums: &[i32]
表示:
一个 i32 切片的引用。
它可以接收数组引用,也可以接收 Vec 引用。
所以相比:
fn sum(nums: &Vec<i32>)
更推荐写成:
fn sum(nums: &[i32])
因为更通用。
11. &str:字符串切片引用
&str 是 Rust 中非常常见的引用类型。
fn print_text(s: &str) {
println!("{}", s);
}
fn main() {
let s1 = String::from("hello");
let s2 = "world";
print_text(&s1);
print_text(s2);
}
这里:
&s1
可以从 String 借用出 &str。
而:
"world"
本身就是 &str。
字符串字面量的类型其实是:
&'static str
意思是它在整个程序运行期间都有效。
12. & 常用于结构体字段,但需要生命周期
如果结构体里保存引用,就要告诉 Rust 这个引用能活多久。
struct User<'a> {
name: &'a str,
}
fn main() {
let name = String::from("张三");
let user = User {
name: &name,
};
println!("{}", user.name);
}
这里:
struct User<'a> {
name: &'a str,
}
表示:
User里面的name是一个引用,它的生命周期是'a。
如果结构体自己拥有数据,通常写成:
struct User {
name: String,
}
如果结构体只是临时借用外部数据,可以写成:
struct User<'a> {
name: &'a str,
}
13. 返回引用时也会用到 &
可以返回参数中的引用:
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b' ' {
return &s[..i];
}
}
s
}
fn main() {
let text = String::from("hello world");
let word = first_word(&text);
println!("{}", word);
}
这里:
fn first_word(s: &str) -> &str
表示:
输入一个字符串引用,返回这个字符串中的一部分引用。
注意,返回的 &str 不是新字符串,而是原字符串的一部分。
但是不能返回函数内部局部变量的引用:
fn get_name() -> &String {
let name = String::from("张三");
&name // 错误
}
原因是 name 在函数结束时会被销毁,返回它的引用就会变成悬垂引用。
正确写法是返回所有权:
fn get_name() -> String {
let name = String::from("张三");
name
}
14. & 会出现在 match 模式匹配中
& 不只是在表达式里取引用,也可以在模式匹配里匹配引用。
fn main() {
let x = 10;
let r = &x;
match r {
&value => println!("{}", value),
}
}
这里:
match r
中的 r 是:
&i32
模式:
&value
表示匹配一个引用,并把引用里面的值绑定到 value。
再看一个例子:
fn main() {
let nums = vec![1, 2, 3];
for &n in &nums {
println!("{}", n);
}
}
这里:
for &n in &nums
等价于:
for n_ref in &nums {
let n = *n_ref;
println!("{}", n);
}
因为 i32 是 Copy 类型,所以可以直接复制出来。
15. & 和 .iter() 的关系
这两个写法很像:
for item in &vec {
println!("{}", item);
}
和:
for item in vec.iter() {
println!("{}", item);
}
基本效果类似,item 都是引用。
完整例子:
fn main() {
let nums = vec![1, 2, 3];
for n in &nums {
println!("{}", n);
}
for n in nums.iter() {
println!("{}", n);
}
}
如果要可变遍历:
fn main() {
let mut nums = vec![1, 2, 3];
for n in &mut nums {
*n += 1;
}
for n in nums.iter_mut() {
*n += 1;
}
println!("{:?}", nums);
}
16. & 常用于 HashMap 查询
很多标准库 API 都用引用,因为查询时不需要拿走所有权。
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("张三"), 90);
scores.insert(String::from("李四"), 80);
let name = String::from("张三");
let score = scores.get(&name);
println!("{:?}", score);
println!("{}", name);
}
这里:
scores.get(&name)
只借用 name 来查询,不会消耗 name。
get 返回的是:
Option<&i32>
也就是:
可能查到一个分数的引用。
17. & 常用于避免大对象复制或移动
例如有一个比较大的结构体:
struct Config {
host: String,
port: u16,
username: String,
password: String,
}
fn connect(config: &Config) {
println!("连接到 {}:{}", config.host, config.port);
}
fn main() {
let config = Config {
host: String::from("127.0.0.1"),
port: 5432,
username: String::from("admin"),
password: String::from("123456"),
};
connect(&config);
println!("配置还能继续使用:{}", config.host);
}
这里函数只需要读取配置,所以用:
&Config
而不是:
Config
这样可以避免所有权转移。
18. 方法调用时经常隐式使用 &
比如:
fn main() {
let s = String::from("hello");
let len = s.len();
println!("{}", len);
}
你写的是:
s.len()
但很多方法本质上接收的是:
&self
例如可以理解为:
impl String {
fn len(&self) -> usize {
// ...
}
}
所以:
s.len()
编译器会自动帮你借用成类似:
String::len(&s)
方法里常见三种 self:
impl User {
fn show(&self) {
// 只读
}
fn rename(&mut self, name: String) {
// 修改自己
}
fn into_name(self) -> String {
// 拿走所有权
self.name
}
}
分别对应:
| 写法 | 含义 |
|---|---|
&self |
不可变借用自己 |
&mut self |
可变借用自己 |
self |
拿走自己的所有权 |
例子:
struct User {
name: String,
}
impl User {
fn show(&self) {
println!("{}", self.name);
}
fn rename(&mut self, name: String) {
self.name = name;
}
fn into_name(self) -> String {
self.name
}
}
fn main() {
let mut user = User {
name: String::from("张三"),
};
user.show();
user.rename(String::from("李四"));
user.show();
let name = user.into_name();
println!("{}", name);
// user.show(); // 错误,user 已经被 into_name 消耗
}
19. & 和自动解引用、自动借用
Rust 编译器在方法调用时会做一些自动处理。
例如:
fn main() {
let s = String::from("hello");
println!("{}", s.len());
}
虽然 len 通常需要 &self,但你不需要手动写:
(&s).len()
编译器会自动帮你借用。
再比如:
fn print_len(s: &str) {
println!("{}", s.len());
}
fn main() {
let text = String::from("hello");
print_len(&text);
}
函数需要的是:
&str
你传的是:
&String
但 Rust 可以通过 Deref 强制转换,把 &String 自动转成 &str。
这叫:
Deref coercion
也就是解引用强制转换。
20. & 在数组、Vec、字符串切片中很常见
数组切片
fn main() {
let nums = [1, 2, 3, 4, 5];
let part = &nums[1..4];
println!("{:?}", part);
}
输出:
[2, 3, 4]
这里:
&nums[1..4]
类型是:
&[i32]
字符串切片
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
println!("{}", hello);
}
这里:
&s[0..5]
类型是:
&str
注意中文字符串不能随便按字节切。
例如:
fn main() {
let s = String::from("你好世界");
let part = &s[0..3];
println!("{}", part);
}
这个可以,因为一个中文字符通常占 3 个字节,0..3 正好是第一个字。
但下面可能会崩溃:
let part = &s[0..2];
因为切到了 UTF-8 字符中间。
21. & 也可以表示“引用类型”
有时候 & 不是在“取引用”,而是在“声明引用类型”。
例如:
let x: i32 = 10; let r: &i32 = &x;
左边:
r: &i32
表示 r 的类型是 i32 的引用。
右边:
&x
表示创建一个对 x 的引用。
所以同一个符号 &,位置不同,含义稍有不同:
| 写法 | 含义 |
|---|---|
&x |
创建一个引用 |
&mut x |
创建一个可变引用 |
&i32 |
i32 的不可变引用类型 |
&mut i32 |
i32 的可变引用类型 |
&str |
字符串切片引用类型 |
&[i32] |
i32 切片引用类型 |
22. & 在生命周期中的写法
当引用涉及生命周期时,会看到:
&'a T
例如:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
这里:
x: &'a str y: &'a str -> &'a str
意思是:
返回值的生命周期和参数
x、y有关系。
完整例子:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("abcd");
let s2 = String::from("xyz");
let result = longest(&s1, &s2);
println!("{}", result);
}
生命周期不是让引用活得更久,而是告诉编译器:
这个引用至少在哪个范围内是有效的。
23. & 还可以是按位与运算符
虽然你问的是引用,但 Rust 里 & 还有另一个含义:按位与。
fn main() {
let a = 0b1100;
let b = 0b1010;
let c = a & b;
println!("{:b}", c);
}
输出:
1000
这里:
a & b
不是引用,而是按位与运算。
区别看位置:
let r = &x; // 引用 let c = a & b; // 按位与
另外逻辑与是:
&&
例如:
if age > 18 && has_id {
println!("可以进入");
}
24. 常见错误一:引用比原值活得更久
错误示例:
fn main() {
let r;
{
let x = 10;
r = &x;
}
println!("{}", r); // 错误
}
原因:
x
在内部作用域结束后已经被销毁,r 不能再引用它。
正确写法:
fn main() {
let x = 10;
let r = &x;
println!("{}", r);
}
25. 常见错误二:可变引用期间使用原变量
错误示例:
fn main() {
let mut x = 10;
let r = &mut x;
println!("{}", x); // 错误
println!("{}", r);
}
因为 r 正在可变借用 x,此时不能直接使用 x。
正确写法:
fn main() {
let mut x = 10;
let r = &mut x;
println!("{}", r);
println!("{}", x);
}
或者:
fn main() {
let mut x = 10;
{
let r = &mut x;
println!("{}", r);
}
println!("{}", x);
}
26. 常见错误三:函数参数该用 &str,却写成 &String
不是说 &String 错,而是很多场景不够通用。
不太推荐:
fn print_text(s: &String) {
println!("{}", s);
}
更推荐:
fn print_text(s: &str) {
println!("{}", s);
}
因为:
fn main() {
let s = String::from("hello");
print_text(&s);
print_text("world");
}
&str 可以同时支持:
String &str 字符串字面量
27. 常见使用场景总结
| 场景 | 常见写法 | 说明 |
|---|---|---|
| 借用变量 | let r = &x; |
只读引用 |
| 可变借用 | let r = &mut x; |
可以修改 |
| 函数只读参数 | fn f(x: &T) |
避免所有权转移 |
| 函数修改参数 | fn f(x: &mut T) |
修改外部变量 |
| 字符串参数 | fn f(s: &str) |
推荐写法 |
| 数组/Vec 参数 | fn f(nums: &[i32]) |
接收切片 |
| 遍历集合 | for x in &vec |
不移动元素 |
| 可变遍历 | for x in &mut vec |
修改元素 |
| 结构体保存引用 | field: &'a str |
需要生命周期 |
| 返回借用数据 | fn f(s: &str) -> &str |
返回原数据的一部分 |
| HashMap 查询 | map.get(&key) |
借用 key 查询 |
| 方法只读自己 | fn f(&self) |
不消耗对象 |
| 方法修改自己 | fn f(&mut self) |
修改对象 |
| 按位与 | a & b |
不是引用,是运算符 |
28. 一个综合例子
struct User {
name: String,
age: u8,
}
impl User {
fn show(&self) {
println!("姓名:{},年龄:{}", self.name, self.age);
}
fn birthday(&mut self) {
self.age += 1;
}
}
fn print_names(users: &[User]) {
for user in users {
println!("{}", user.name);
}
}
fn change_first_user(users: &mut [User]) {
if let Some(user) = users.first_mut() {
user.name.push_str(" VIP");
}
}
fn main() {
let mut users = vec![
User {
name: String::from("张三"),
age: 20,
},
User {
name: String::from("李四"),
age: 25,
},
];
print_names(&users);
change_first_user(&mut users);
for user in &users {
user.show();
}
users[0].birthday();
users[0].show();
}
这里包含了很多 & 的典型用法:
&self &mut self &[User] &mut [User] &users &mut users for user in &users
29. 最后记忆口诀
可以这样记:
&T 借来看,只读,不拿走 &mut T 借来改,独占,可修改 T 拿走所有权
函数参数设计时,可以这样判断:
只是读取:用 &T / &str / &[T] 需要修改:用 &mut T / &mut [T] 需要拥有:用 T / String / Vec<T>
尤其常见推荐:
fn read_text(s: &str) // 读字符串 fn read_list(nums: &[i32]) // 读列表 fn update(s: &mut String) // 修改字符串 fn consume(s: String) // 拿走所有权
一句话总结:
Rust 里的 & 是借用系统的核心。它让你可以安全地“使用数据”,但不一定“拥有数据”;配合 &mut,还可以在编译期保证修改数据时不会出现并发混乱或悬垂引用。