Day 6: 引用与借用
今天是《30天精通Rust语言》系列的第六天。我们将学习Rust中极其重要的概念——引用(References)和借用(Borrowing)。引用允许你在不获取值所有权的情况下使用值,这是Rust最强大也最独特的特性之一。
2. 引用基础
Section titled “2. 引用基础”引用允许你暂时借用一个值的使用权,而不获取其所有权。用 & 创建不可变引用,用 &mut 创建可变引用。
2.1 不可变引用
Section titled “2.1 不可变引用”fn main() { let s = String::from("hello"); let len = calculate_length(&s);
println!("The length of '{}' is {}", s, len);}
fn calculate_length(s: &String) -> usize { s.len()}在这个例子中,&s 表示创建对 s 的引用并传递给函数。函数calculate_length并不拥有这个字符串,它只是借用它。
2.2 可变引用
Section titled “2.2 可变引用”当需要修改借用的值时,使用可变引用:
fn main() { let mut s = String::from("hello"); change(&mut s); println!("{}", s);}
fn change(s: &mut String) { s.push_str(", world");}注意:可变变量 s 才能创建可变引用,且需要显式声明 &mut。
3. 借用规则
Section titled “3. 借用规则”Rust的借用规则确保了内存安全:
- 任意数量的不可变引用:可以同时拥有多个不可变引用。
- 只有一个可变引用:可变引用与不可变引用不能同时存在,且同一作用域只能有一个可变引用。
- 引用必须总是有效的:不能创建悬垂引用(dangling reference)。
3.1 规则验证:多个不可变引用
Section titled “3.1 规则验证:多个不可变引用”fn main() { let s = String::from("hello");
let r1 = &s; let r2 = &s; let r3 = &s;
println!("{}, {}, {}", r1, r2, r3);}3.2 规则验证:可变与不可变引用冲突
Section titled “3.2 规则验证:可变与不可变引用冲突”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; // 现在可以了 r3.push_str(", world");}4. 悬垂引用(Dangling References)
Section titled “4. 悬垂引用(Dangling References)”悬垂引用是指指向一个已被释放内存的引用。Rust的借用规则确保编译器能检测并阻止悬垂引用的创建:
fn main() { let reference_to_nothing = dangle();}
fn dangle() -> &String { let s = String::from("hello"); &s // 编译错误!s将在函数结束时被drop,返回的引用无效}编译错误:
error[E0106]: missing lifetime specifierRust在编译时就拒绝了这种危险的代码,而不像C/C++那样在运行时产生未定义行为。
5. 借用的实际应用
Section titled “5. 借用的实际应用”5.1 修改传递给函数的值
Section titled “5.1 修改传递给函数的值”利用可变引用,函数可以修改外部变量:
fn append_elements(elements: &mut Vec<i32>) { elements.push(1); elements.push(2); elements.push(3);}
fn main() { let mut v = Vec::new(); append_elements(&mut v); println!("{:?}", v); // [1, 2, 3]}5.2 同时使用多个借用
Section titled “5.2 同时使用多个借用”合理安排借用的作用域,可以同时满足借用规则:
fn main() { let mut s = String::from("hello");
let r1 = &s; let r2 = &s; println!("{} and {}", r1, r2); // r1和r2在这里使用完毕
let r3 = &mut s; // 可以创建可变引用了 r3.push_str(", world"); println!("{}", r3);}6. 借用检查器的工作原理
Section titled “6. 借用检查器的工作原理”Rust的借用检查器(Borrow Checker)是编译器的一部分,负责分析代码中的借用是否遵循规则。它通过追踪变量的引用和作用域来判断代码是否安全。
理解借用检查器的一个技巧:引用的作用域结束于最后一次使用该引用的地方,而非函数或代码块的末尾。
7. 借用与方法调用
Section titled “7. 借用与方法调用”对象的方法可以借用对象,也可以获取对象的所有权:
struct Rectangle { width: u32, height: u32,}
impl Rectangle { // &self 不可变借用 fn area(&self) -> u32 { self.width * self.height }
// &mut self 可变借用 fn scale(&mut self, factor: u32) { self.width *= factor; self.height *= factor; }}
fn main() { let mut rect = Rectangle { width: 10, height: 5 };
println!("Area: {}", rect.area()); // 50
rect.scale(2); println!("Scaled: {}x{}", rect.width, rect.height); // 20x10}8. 实践练习
Section titled “8. 实践练习”今天的实践任务:
- 编写函数接收两个
&String参数,返回较长的字符串的引用。 - 编写程序验证:创建两个不可变引用后尝试创建可变引用,观察编译错误。
- 编写程序验证悬垂引用:尝试返回局部变量的引用,观察编译错误。
- 实现一个
Counter结构体,提供add(&mut self)方法增加计数,验证可变借用的行为。 - 编写程序处理一个字符串向量,使用不可变引用遍历,可变引用修改特定元素。
9. 常见问题
Section titled “9. 常见问题”问:为什么Rust要限制可变引用的数量?
答:同时存在多个可变引用可能导致数据竞争(data race)。Rust的设计目标是在编译时消除数据竞争,即使这意味着更严格的借用规则。
问:是否可以返回借用的引用而不是值?
答:可以,但需要使用生命周期注解(下一章内容),确保返回的引用与输入引用的生命周期关联。
10. 总结
Section titled “10. 总结”今天我们深入学习了Rust的引用与借用:
- 引用用
&创建不可变借用,用&mut创建可变借用。 - 借用规则:多个不可变引用 OR 一个可变引用(不能同时存在)。
- 借用规则在编译时就消除悬垂引用和数据竞争。
- 理解引用的作用域对于编写正确代码至关重要。
明天我们将学习结构体(Struct),了解如何创建自定义数据类型。加油!