1.1 引用:借来看看,不过户
上一章的痛点:函数想用一下值,就得把所有权交出去。引用的解决思路:借用(borrow)------我看看/用用,东西还是你的。
rust
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // &s1 是"指向 s1 的引用"
println!("'{}' 的长度是 {}", s1, len); // ✅ s1 还能用!
}
fn calculate_length(s: &String) -> usize { // 参数类型是引用
s.len()
} // s 离开作用域,但它不拥有值,所以什么都不释放

&s1创建一个指向 s1 值的引用,不取得所有权- 引用离开作用域时,指向的值不会被释放
- 创建引用的行为叫借用
内存示意:
栈 堆
s (引用) ─────┐
↓
s1 [ptr,len,cap] ──→ "hello"
1.2 可变引用 &mut
借来还想改?用可变引用:
rust
fn main() {
let mut s = String::from("hello"); // 原变量本身必须可变
change(&mut s); // 可变借用
println!("{}", s); // hello, world
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}

1.3 借用的两条铁律
规则 1 :同一作用域内,要么有任意多个不可变引用 ,要么只有一个可变引用 ------不能混用。
规则 2 :引用必须始终有效(不许悬垂)。
规则 1 演示:可变与不可变不能共存
rust
let mut s = String::from("hello");
let r1 = &s; // ✅ 不可变借用
let r2 = &s; // ✅ 再来几个也行
println!("{} {}", r1, r2);
// ↑ r1、r2 最后一次使用在这里,之后它们的借用结束
let r3 = &mut s; // ✅ 此时没有不可变借用活着,可以可变借用
r3.push_str("!");
println!("{}", r3);

但如果交错使用就会报错:
rust
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s; // ❌ 编译错误!
// println!("{}", r1); // r1 之后还要用,和可变借用冲突

为什么这样规定? 这就是 Rust 在编译期消灭数据竞争的方式:一个人写的时候绝不允许任何人读。多线程里最臭名昭著的 bug 类,在 Rust 里根本编译不过。
规则 2 演示:悬垂引用
rust
// ❌ 编译错误
fn dangle() -> &String {
let s = String::from("hello");
&s // 返回指向 s 的引用
} // 但 s 在这里被释放了!引用指向了无效内存
编译器报错:this function's return type contains a borrowed value, but there is no value for it to borrow from。
修复:直接返回值(转移所有权),而不是返回引用:
rust
fn no_dangle() -> String {
String::from("hello") // ✅ 所有权移交出去
}
1.4 字符串切片 &str
切片(slice) 是"指向集合中一段连续元素"的引用,同样不取得所有权。
rust
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"(含头不含尾)
let world = &s[6..11]; // "world"
// 等价简写
let a = &s[..5]; // 从 0 开始
let b = &s[6..]; // 到结尾
let c = &s[..]; // 整个字符串
切片的内部结构:[起始指针 + 长度],依然只是"看",不拥有数据。
实用示例:取第一个单词
rust
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i]; // 找到空格,返回它前面的部分
}
}
&s[..] // 没有空格,整个就是单词
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("第一个单词:{}", word); // hello
}

注意参数类型用的是 &str 而不是 &String------这是惯例:
rust
// &str 作为参数,String 和 &str 都能传进来(自动 deref 转换)
let s1 = String::from("hello");
let s2 = "world";
first_word(&s1); // ✅ &String 自动转成 &str
first_word(s2); // ✅ 本来就是 &str
字符串字面量的真相
rust
let s = "hello"; // 类型是 &str ------ 指向编译进二进制的字符串的切片!
这就是为什么字面量天生不可变:它是不可变引用。
1.5 其他切片
数组也能切片:
rust
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..4]; // [2, 3, 4]
println!("{:?}", slice);
1.6 借用与修改的经典冲突
切片会"锁定"原数据,防止你改它:
rust
let mut s = String::from("hello world");
let word = first_word(&s);
// s.clear(); // ❌ 错误:clear 需要可变借用,但 word 还在借用 s
println!("{}", word);
把 s.clear() 放到 println! 之后就合法了(word 不再被使用,借用结束)。这个机制保证:word 永远指向有效的数据------编译器替你盯着。
小结
&T不可变引用(可多个)、&mut T可变引用(同时只能一个),二者不能同时存活- 引用不能悬垂:不许返回指向局部变量的引用
- 字符串切片
&str是最常用的字符串类型;函数参数优先用&str - 字符串字面量就是
&str - 借用规则是 Rust 编译期防数据竞争的武器