不灭的焱

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

作者:AlbertWen  添加时间:2026-07-06 00:47:44  修改时间:2026-07-08 01:15:42  分类:01.Rust编程  编辑

核心原理一句话:

for n in &vec 拿到的是 &T,不是 T。要不要写 *n,取决于你后面这个地方需要的是“引用”还是“值本身”。

1. 先看最基础例子

fn main() {
    let nums = vec![1, 2, 3];

    for n in &nums {
        println!("{}", n);
    }
}

这里:

for n in &nums

等价于:

for n in nums.iter()

所以 n 的类型是:

&i32

不是:

i32

也就是说,循环里的 n 是每个元素的引用。

2. *n 是什么意思?

let nums = vec![1, 2, 3];

for n in &nums {
    let value = *n;
    println!("{}", value);
}

这里:

*n

表示:

把引用 n 解开,拿到它指向的真实值。

如果:

n: &i32

那么:

*n: i32

所以可以理解为:

n   是引用
*n  是引用指向的值

3. 为什么有时候不用 * 也能打印?

比如:

fn main() {
    let nums = vec![1, 2, 3];

    for n in &nums {
        println!("{}", n);
    }
}

这里 n&i32,但也能打印。

原因是 println! 很智能,Rust 标准库里对很多引用类型也实现了格式化能力。

也就是说:

println!("{}", n);

可以打印引用指向的内容。

你写成下面这样也可以:

println!("{}", *n);

这两个对于 i32 来说效果一样。

4. 什么时候必须用 *

当你要把引用里的值当成真正的值来用时,通常需要 *

例如:

fn main() {
    let nums = vec![1, 2, 3];

    for n in &nums {
        let x: i32 = *n;
        println!("{}", x);
    }
}

因为:

n

是:

&i32

而变量 x 需要的是:

i32

所以要写:

let x: i32 = *n;

再比如做计算:

fn main() {
    let nums = vec![1, 2, 3];

    for n in &nums {
        let result = *n + 10;
        println!("{}", result);
    }
}

这里推荐写:

*n + 10

因为你要对真实的数字做加法。

5. 修改 Vec 里的值时,经常必须用 *

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;

也就是:

把 n 指向的那个 i32 乘以 10

不能写:

n *= 10;

因为 n 本身是一个引用,不是数字。

6. 但是调用方法时,很多时候不用 *

比如:

fn main() {
    let names = vec![
        String::from("zhangsan"),
        String::from("lisi"),
    ];

    for name in &names {
        println!("{}", name.len());
    }
}

这里:

name

的类型是:

&String

但是你可以直接写:

name.len()

不用写:

(*name).len()

原因是 Rust 在方法调用时会自动解引用。

也就是说,编译器会帮你把:

name.len()

理解成类似:

(*name).len()

这叫:

自动解引用
auto deref

所以这两个写法都可以:

name.len();
(*name).len();

但实际开发中通常写:

name.len();

更自然。

7. 可变引用调用方法时,也经常不用 *

fn main() {
    let mut names = vec![
        String::from("张三"),
        String::from("李四"),
    ];

    for name in &mut names {
        name.push_str("同学");
    }

    println!("{:?}", names);
}

这里:

name

的类型是:

&mut String

但是可以直接调用:

name.push_str("同学");

不用写:

(*name).push_str("同学");

因为方法调用会自动解引用。

8. 但是如果你要整体替换这个值,要用 *

比如:

fn main() {
    let mut names = vec![
        String::from("张三"),
        String::from("李四"),
    ];

    for name in &mut names {
        if name == "张三" {
            *name = String::from("王五");
        }
    }

    println!("{:?}", names);
}

这里:

*name = String::from("王五");

表示:

name 这个引用指向的原始 String 替换掉。

不能写:

name = String::from("王五");

因为 name 是引用变量,不是 String 本身。

9. for &n in &nums 是另一种写法

对于 i32 这种简单类型,可以这样写:

fn main() {
    let nums = vec![1, 2, 3];

    for &n in &nums {
        println!("{}", n + 10);
    }
}

注意这里是:

for &n in &nums

不是:

for n in &nums

区别是:

for n in &nums

此时:

n: &i32

而:

for &n in &nums

此时:

n: i32

这个 &n模式匹配,意思是:

循环时把 &i32 解开,直接拿到里面的 i32

所以:

for &n in &nums {
    println!("{}", n + 10);
}

等价于:

for n_ref in &nums {
    let n = *n_ref;
    println!("{}", n + 10);
}

10. 为什么 Vec<String> 不能随便 *name

看这个例子:

fn main() {
    let names = vec![
        String::from("张三"),
        String::from("李四"),
    ];

    for name in &names {
        let s = *name; // 错误
        println!("{}", s);
    }
}

为什么错误?

因为:

name: &String

*name 得到的是 String

但是 String 不是简单复制类型,不能从引用后面直接把它搬出来。

如果允许这样做,就相当于把 Vec 里的字符串移动走了。

正确写法有几种。

只是读取,用引用

for name in &names {
    println!("{}", name);
}

需要复制一份,用 clone

for name in &names {
    let s = name.clone();
    println!("{}", s);
}

这里 clone() 会复制一份新的 String

如果真的要拿走所有权,不要用 &names

for name in names {
    println!("{}", name);
}

这样 name 的类型就是:

String

但是循环结束后,原来的 names 就不能再用了。

11. 为什么 i32 可以 *nString 不行?

因为 i32 实现了 Copy

let nums = vec![1, 2, 3];

for n in &nums {
    let x = *n; // 可以
}

这里不是把 Vec 里的数字移动走,而是复制了一份。

但是 String 没有实现 Copy

let names = vec![String::from("张三")];

for name in &names {
    let s = *name; // 错误
}

因为 String 管理堆内存,不能随便隐式复制。

总结:

类型 *引用 后能不能直接赋值给新变量 原因
i32 可以 i32Copy
bool 可以 boolCopy
char 可以 charCopy
String 不可以 String 不是 Copy
Vec<T> 不可以 Vec<T> 不是 Copy
自定义 struct 看是否实现 Copy 默认不是 Copy

12. 三种遍历方式对比

假设:

let nums = vec![1, 2, 3];

方式一:拿走所有权

for n in nums {
    println!("{}", n);
}

此时:

n: i32

循环后 nums 不能用了。

方式二:不可变借用

for n in &nums {
    println!("{}", n);
}

此时:

n: &i32

循环后 nums 还能继续用。

方式三:可变借用

for n in &mut nums {
    *n += 1;
}

此时:

n: &mut i32

可以修改 Vec 里的元素。

13. 什么时候不用 *

常见不用 * 的情况:

1. 只是打印

println!("{}", n);

2. 调用方法

name.len();
name.push_str("abc");

方法调用会自动解引用。

3. 函数本来就需要引用

fn print_num(n: &i32) {
    println!("{}", n);
}

fn main() {
    let nums = vec![1, 2, 3];

    for n in &nums {
        print_num(n);
    }
}

这里 n 已经是 &i32,所以不用 *

如果你写:

print_num(*n);

反而错,因为函数要的是 &i32,你给的是 i32

14. 什么时候要用 *

常见需要 * 的情况:

1. 需要真实值

let x: i32 = *n;

2. 做数值计算

let result = *n + 10;

3. 修改引用指向的值

*n += 1;

4. 替换引用指向的对象

*name = String::from("新名字");

15. 最实用的判断方法

你可以按这个规则判断:

n 是 &T

如果后面需要 &T:不用 *
如果后面需要 T:用 *
如果是方法调用:通常不用 *,Rust 会自动解引用
如果要修改引用指向的值:通常要用 *

比如:

for n in &nums {
    println!("{}", n);       // 不用 *,打印引用也可以
    let x = *n;              // 要 i32,所以用 *
    let y = *n + 1;          // 要计算,所以用 *
    print_ref(n);            // 函数要 &i32,不用 *
    print_value(*n);         // 函数要 i32,用 *
}

完整例子:

fn print_ref(n: &i32) {
    println!("引用:{}", n);
}

fn print_value(n: i32) {
    println!("值:{}", n);
}

fn main() {
    let nums = vec![1, 2, 3];

    for n in &nums {
        print_ref(n);
        print_value(*n);
    }
}

16. 一句话总结

for n in &vec 时:

n 是引用

所以:

n   // 引用本身,类型是 &T
*n  // 引用指向的值,类型是 T

但是 Rust 在一些场景会帮你自动处理,比如:

println!("{}", n);
name.len();
name.push_str("abc");

所以你会感觉“有时候要 *,有时候不用 *”。

最关键的判断是:

当前这个地方到底需要的是引用,还是引用背后的真实值?

需要引用,就不用 *

需要真实值,就用 *