不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-06 00:16:30  修改时间:2026-07-07 23:19:03  分类:01.Rust编程  编辑

下面把 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);
}

因为 i32Copy 类型,所以可以直接复制出来。

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

意思是:

返回值的生命周期和参数 xy 有关系。

完整例子:

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,还可以在编译期保证修改数据时不会出现并发混乱或悬垂引用。