首页 / Rust 入门教程 / 引用与借用

Rust 入门教程

引用与借用

本教程共 78 篇 · 第 22 篇 · 更新于 2026-08-08 · 约 11 分钟阅读

RustRust 入门教程引用借用reference解引用所有权悬垂引用

本节目标:用 & 创建引用,实现”借用”——不转移所有权就能让别人用你的值,分清引用和原值,认识解引用运算符 *,并理解编译器如何天然阻止悬垂引用。

上一章的移动有个烦恼:把 String 传给函数,原变量就失效了。可很多时候我只是想”让别人看一眼、用一下”这个值,并不想交出所有权。Rust 用引用(reference)解决这个问题,术语叫借用(borrowing):把值借出去用,用完还回来,所有权始终在自己手里。生活里就像你借把伞给朋友,他不拥有伞,用完得还你。

22-1 用 & 借出去

看例子,函数想打印字符串长度,但不想拿走它:

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s); // 传引用 &s,借用
    println!("'{s}' 的长度是 {len}"); // s 仍然可用!
}

fn calculate_length(text: &String) -> usize {
    text.len()
} // text 是引用,离开作用域不会 drop 原字符串

&s 创建一个指向 s 的引用,类型是 &String。把它传给函数,函数拿到的是”借用”,不是所有权。函数结束,text 这个引用消失,但底下的 s 毫发无损,main 里还能继续用。这就是借用的妙处:我指着我的东西跟你说”你看,这东西长这样”,你看完我还在拿着。

Note

& 是”取引用”(借出),&String 读作”String 的引用”。它像 C 的指针,但不允许为空、且受借用规则约束,比裸指针安全得多。你不用担心它指向乱七八糟的地址。

22-2 借用不转移所有权

对比移动:移动后原主失效;借用后原主健在。为什么 calculate_length 结束时不会释放字符串?因为它持有的只是引用(&String),不是真正的所有者。只有所有者离开作用域才 drop。引用只是个”旁观者”,没有释放权。

这点决定了函数的写法风格:如果一个函数只是读取数据,它该接收引用,而不是把数据”吃掉”。这样调用方传完还能继续用原值,体验最好。

22-3 解引用:用 * 取值

引用指向值,但要拿到它指向的那个值本身,用解引用运算符 *

fn main() {
    let x = 5;
    let r = &x;     // r 是 &i32,指向 x
    println!("r 指向的值是 {}", *r); // *r 取出 5
}

*r 表示”r 指向的那个值”。多数时候你不用手动解引用,因为 Rust 有”自动解引用”,比如 text.len()text&String,编译器自动帮你解引用去调方法。但理解 * 的含义,对读懂报错和进阶指针很有帮助。

Warning

初学者容易把 &* 搞混:& 是”拿到指向某值的引用”(往外指),* 是”顺着引用拿到里面的值”(往里取)。一个方向相反的配对。可以记成:& 是”我要借”,* 是”我要取”。

22-4 不可变引用:默认只读

上面的 &s不可变引用。顾名思义,通过它你不能修改原值:

fn change(text: &String) {
    // text.push_str("!"); // 错!不能通过不可变引用修改
}

想改,得用可变引用 &mut,那是下一章的主题。这一章先记住:默认借来的都是只读的,编译器保证”借出去看一眼”不会偷偷改坏你的东西。这点和现实一致——你借给别人一本不能涂改的书,对方只能读。

22-5 可以有多个不可变借用

只读的借用很安全,所以允许同时存在多个:

let s = String::from("hi");
let r1 = &s;
let r2 = &s; // 多个不可变引用,OK
println!("{} 和 {}", r1, r2);

大家都只是看,互不干扰,编译器放行。这点和可变引用形成鲜明对比(下一章讲冲突)。因为只读不会产生不一致,所以”多个读者”是被允许的。

22-6 一个常见场景:函数只读不拿

写工具函数时,凡是”只读取、不修改、不长期持有”的参数,都应该用引用传,而不是移动:

fn print_upper(s: &str) { // 用 &str 更通用,后面字符串章节讲
    println!("{}", s.to_uppercase());
}

这样调用方传完还能继续用原值,体验最好。养成习惯:函数参数默认考虑”能不能用引用”,除非你确实要拿走所有权。这条习惯能避免大量”传完就不能用”的尴尬。

22-7 借用何时失效(活跃区间)

引用的有效范围,是它被创建后、最后一次使用之前的那段代码。编译器用一套复杂的”借用检查”追踪这个,保证引用不会指向已释放的内存。比如下面这种它绝不会允许:

let r;
{
    let s = String::from("temp");
    r = &s; // 试图借用一个马上要消失的值
} // s 在这里 drop 了
// println!("{r}"); // 错!r 成了悬垂引用,编译器拦下

这种”指向已释放内存的引用”叫悬垂引用(dangling reference)。别的语言里它是隐蔽大 bug,Rust 在编译期就报红,根本不让你编过。

Tip

编译器报错 “borrowed value does not live long enough” 或 “x does not live long enough”,就是引用活得太久、指向的东西先没了。解决办法通常是让被借的值活得更久(调整作用域),或让函数返回拥有的数据而非引用。

22-8 借用在真实函数里

借用在真实代码里随处可见。比如写一个”统计字符串里空格数”的辅助函数,它只需读字符串、不该拿走它:

fn count_spaces(text: &str) -> usize {
    text.chars().filter(|c| *c == ' ').count()
}

fn main() {
    let s = String::from("hello world");
    println!("空格数:{}", count_spaces(&s)); // 借出,s 仍可继续用
    println!("原串还在:{s}");
}

count_spaces 接收 &str(字符串的不可变借用),看完返回个数,不碰所有权。调用方传 &s,用完 s 还在。这种”只读就借、要拥有才移动”的直觉,是写出地道 Rust 的关键。

Tip

函数参数该用 &T 还是 T?经验:函数只是读取/使用这个值、不打算长期持有它,就用引用 &T(或 &mut T);函数要”收编”这个值、之后调用方不再需要它,才用 T 拿走所有权。绝大多数工具函数属于前者,所以你会看到大量 & 参数。

22-9 借用与生命周期的预告

前面提到”引用不能指向已释放的内存”,编译器靠一套叫”生命周期(lifetime)“的机制追踪每个引用能活多久。入门阶段你几乎不用手写生命周期标注——编译器能自动推断绝大多数情况。只有写很通用的函数或库时,才需要手动标注(如 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str)。

Note

生命周期听着吓人,但日常写应用代码时编译器替你管好了,你感受不到它的存在。它存在的目的,就是保证”借出去的引用,在主人还活着的时候一直有效”。等写到需要返回引用的通用函数时,再正式学生命周期也不迟。

Tip

如果你现在遇到生命周期相关的报错,先别钻牛角尖去学语法,多半是”函数想返回引用、但该返回拥有的值”。把返回类型从 &T 改成 T(返回 String 而非 &str),往往就解决了。

22-10 小结

&值 创建引用(借用),不转移所有权,原主仍可用;引用是只读的,多个不可变引用可共存;* 解引用取值;编译器禁止悬垂引用。但只读不够——有时函数就是要改你的值。这就需要下一章的可变引用 &mut,以及它那条”同一时刻要么一个可变、要么多个不可变”的铁律。