借用与引用
借用(Borrowing)是 Rust 所有权系统的重要组成部分,它允许我们使用值而不获取其所有权。通过引用,我们可以安全地访问数据而不移动它。
什么是引用
引用就像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用确保指向某个特定类型的有效值。
基本引用
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 是对 String 的引用
s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,所以什么也不会发生
引用的规则
Rust 的借用检查器确保引用总是有效的,基于以下规则:
- 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
- 引用必须总是有效的
不可变引用
多个不可变引用
rust
fn main() {
let s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &s; // 没问题
println!("{}, {}, {}", r1, r2, r3);
// 可以同时存在多个不可变引用
print_string(&s);
print_string(&s);
print_string(&s);
}
fn print_string(s: &String) {
println!("字符串:{}", s);
}
不可变引用的限制
rust
fn main() {
let s = String::from("hello");
let r = &s;
// 不能通过不可变引用修改值
// r.push_str(", world"); // 错误!
println!("引用:{}", r);
}
可变引用
基本可变引用
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");
}
可变引用的限制
rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s; // 没问题
// let r2 = &mut s; // 错误!不能同时有两个可变引用
println!("{}", r1);
// 在 r1 使用完后,可以创建新的可变引用
let r2 = &mut s; // 现在没问题
println!("{}", r2);
}
可变引用与不可变引用不能共存
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
// let r3 = &mut s; // 错误!不能在有不可变引用时创建可变引用
println!("{} and {}", r1, r2);
// 变量 r1 和 r2 不会再被使用
let r3 = &mut s; // 现在没问题
println!("{}", r3);
}
引用的作用域
非词法作用域生命周期(NLL)
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // 没问题
println!("{}", r3);
}
引用的生命周期
rust
fn main() {
let r;
{
let x = 5;
r = &x; // 错误!x 的生命周期比 r 短
} // x 在这里离开作用域
// println!("r: {}", r); // 错误!悬垂引用
}
函数中的引用
参数引用
rust
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("第一个单词:{}", word);
let mut s2 = String::from("hello world");
let word2 = first_word_mut(&mut s2);
word2.make_ascii_uppercase();
println!("修改后:{}", s2);
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn first_word_mut(s: &mut String) -> &mut str {
let len = s.len();
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &mut s[0..i];
}
}
&mut s[0..len]
}
返回引用
rust
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
let result = longest(&s1, &s2);
println!("最长的字符串是:{}", result);
}
// 需要生命周期参数
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
解引用
基本解引用
rust
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y); // 解引用 y 来获取值
// 字符串引用
let s = String::from("hello");
let r = &s;
println!("字符串:{}", s);
println!("引用:{}", r);
println!("解引用:{}", *r); // 不常用,因为 String 实现了 Display
}
自动解引用
rust
fn main() {
let s = String::from("hello world");
let r = &s;
// 这些调用是等价的
println!("长度1:{}", s.len());
println!("长度2:{}", r.len()); // 自动解引用
println!("长度3:{}", (*r).len()); // 显式解引用
}
引用模式
结构体中的引用
rust
struct Person<'a> {
name: &'a str,
age: u32,
}
fn main() {
let name = String::from("Alice");
let person = Person {
name: &name,
age: 30,
};
println!("姓名:{},年龄:{}", person.name, person.age);
}
方法中的引用
rust
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 不可变借用 self
fn area(&self) -> u32 {
self.width * self.height
}
// 可变借用 self
fn double(&mut self) {
self.width *= 2;
self.height *= 2;
}
// 获取 self 的所有权
fn into_square(self) -> Rectangle {
let size = std::cmp::max(self.width, self.height);
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let mut rect = Rectangle {
width: 30,
height: 50,
};
println!("面积:{}", rect.area());
rect.double();
println!("翻倍后面积:{}", rect.area());
let square = rect.into_square(); // rect 被移动
println!("正方形面积:{}", square.area());
}
常见错误和解决方案
悬垂引用
rust
// 错误示例
fn dangle() -> &String { // 返回字符串的引用
let s = String::from("hello"); // s 是一个新字符串
&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。危险!
// 正确做法
fn no_dangle() -> String {
let s = String::from("hello");
s // 直接返回字符串,转移所有权
}
借用检查器错误
rust
fn main() {
let mut data = vec![1, 2, 3, 4, 5];
// 错误:同时存在可变和不可变引用
// let first = &data[0];
// data.push(6); // 错误!
// println!("第一个元素:{}", first);
// 正确做法1:分开使用
let first = data[0]; // 复制值而不是借用
data.push(6);
println!("第一个元素:{}", first);
// 正确做法2:限制引用作用域
{
let first = &data[0];
println!("第一个元素:{}", first);
} // first 的作用域结束
data.push(7); // 现在可以修改
}
最佳实践
选择合适的引用类型
rust
// 优先使用不可变引用
fn read_data(data: &Vec<i32>) {
for item in data {
println!("{}", item);
}
}
// 只在需要修改时使用可变引用
fn modify_data(data: &mut Vec<i32>) {
data.push(42);
}
// 只在需要所有权时获取所有权
fn consume_data(data: Vec<i32>) -> i32 {
data.iter().sum()
}
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5];
read_data(&numbers); // 借用
modify_data(&mut numbers); // 可变借用
let sum = consume_data(numbers); // 转移所有权
println!("总和:{}", sum);
}
避免过度借用
rust
fn main() {
let s = String::from("hello");
// 不好:不必要的引用
let len = calculate_length(&s);
// 更好:直接传递值(如果类型实现了 Copy)
let x = 5;
let doubled = double(x); // 而不是 double(&x)
println!("长度:{},翻倍:{}", len, doubled);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn double(x: i32) -> i32 {
x * 2
}
练习
练习 1:引用基础
编写一个函数,接受两个字符串引用,返回较长的那个。
练习 2:可变引用
编写一个函数,接受一个可变向量引用,移除所有偶数。
练习 3:借用检查器
修复以下代码中的借用检查器错误:
rust
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
练习 4:引用与所有权
重写以下函数,使其不获取参数的所有权:
rust
fn process_string(s: String) -> usize {
s.len()
}
下一步
掌握了借用与引用后,您可以继续学习:
借用是 Rust 内存安全的关键机制,理解它将帮助您编写更安全、更高效的代码!