Rust 借用:& 引用与生命周期标注
Rust 的所有权系统是其核心特性之一,其中 借用检查器 确保内存安全而不需要垃圾回收器。理解引用(&)和生命周期是掌握 Rust 的关键。
理解引用
在 Rust 中,传递 数据所有权通常意味着移动数据,这会导致原始变量失效。使用 引用可以避免这种情况。
let x = 5;
let y = &x; // y 是对 x 的引用
引用使用 & 符号创建,它允许你借用数据而不获取所有权。引用分为可变引用 (&mut) 和不可变引用 (&)。
创建 不可变引用:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
注意:s1 仍然有效,因为它没有被移动。
引用与可变性
默认情况下,引用不可变。声明 可变引用需要使用 &mut:
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
遵循 Rust 的规则:同一时间你只能有一个对特定数据的可变引用,或者任意数量的不可变引用,但不能同时有可变引用和不可变引用。
生命周期介绍
生命周期是 Rust 引用最重要的概念之一。定义 生命周期为引用的有效范围。理解 生命周期可以帮助你避免悬垂引用问题。
观察 这个会导致编译错误的代码:
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
编译器会报错,因为它无法确定返回的引用会存活多久。
添加 生命周期注解:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
这里 'a 是一个生命周期参数,它告诉编译器返回的引用至少与输入的引用一样长。
生命周期规则
记住 三条主要规则:
- 每个引用 都有一个生命周期
- 函数或方法 的参数生命周期必须与返回值生命周期相关联
- 编译器 会使用这些规则确定代码是否有效
结构体中的生命周期
定义 包含引用的结构体时,必须为每个引用指定生命周期:
struct Book<'a> {
title: &'a str,
author: &'a str,
}
fn main() {
let title = String::from("The Rust Programming Language");
let author = String::from("Steve Klabnik");
let book = Book {
title: &title,
author: &author,
};
println!("{} by {}", book.title, book.author);
}
方法定义中的生命周期
为 带有引用的结构体方法添加生命周期:
impl<'a> Book<'a> {
fn summary(&self) -> &'a str {
format!("{} by {}", self.title, self.author)
}
}
静态生命周期
使用 'static 生命周期表示引用会一直存活到程序结束:
let s: &'static str = "I have a static lifetime.";
字符串字面量拥有 'static 生命周期,因为它们存储在程序的只读内存中。
实际应用示例
让我们构建 一个更复杂的例子,展示生命周期的实际应用:
struct Article<'a> {
title: &'a str,
content: &'a str,
author: &'a str,
}
impl<'a> Article<'a> {
fn new(title: &'a str, content: &'a str, author: &'a str) -> Self {
Article {
title,
content,
author,
}
}
fn get_summary(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
fn find_longest_article<'a>(articles: Vec<&'a Article>) -> Option<&'a Article> {
articles.iter().max_by_key(|article| article.content.len())
}
fn main() {
let title = String::from("Rust Ownership");
let content = String::from("In Rust, each value has a variable that’s its owner...");
let author = String::from("Rust Team");
let article1 = Article::new(&title, &content, &author);
let title2 = String::from("Borrowing in Rust");
let content2 = String::from("Rust's borrow checker ensures memory safety...");
let author2 = String::from("Rust Community");
let article2 = Article::new(&title2, &content2, &author2);
let articles = vec![&article1, &article2];
match find_longest_article(articles) {
Some(article) => println!("Longest article: {}", article.get_summary()),
None => println!("No articles found"),
}
}
注意 我们如何在 find_longest_article 函数中使用生命周期参数,以确保返回的文章引用不会比输入的切片活得长。
常见借用错误
悬垂引用
避免 创建悬垂引用,即引用指向已经被释放的数据:
// 错误示例
fn dangle() -> &String {
let s = String::from("hello");
&s // s 在这里被释放,返回的引用将无效
}
解决 方法是返回拥有权的值:
fn no_dangle() -> String {
let s = String::from("hello");
s // 返回 s,所有权被转移
}
可变引用与不可变引用冲突
理解 为什么不能同时使用可变和不可变引用:
let mut s = String::from("hello");
let r1 = &s; // 不可变引用
let r2 = &mut s; // 错误:不能同时有可变和不可变引用
多个可变引用
注意 同一时间只能有一个可变引用:
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // 错误:不能同时有两个可变引用
生命省略规则
了解 Rust 的生命省略规则,让你编写更简洁的代码:
- 输入参数 生命周期:每个引用参数都有其自己的生命周期
- 方法:第一个参数的生命周期名为
self,且省略不写 - 返回类型:
- 如果只有一个输入生命周期,返回类型默认使用它
- 如果有多个输入生命周期,且其中一个是
&self或&mut self,返回类型默认使用self的生命周期
应用 生命省略规则:
// 生命周期省略前的代码
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 first_word<'a>(s: &'a str) -> &'a str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
通过掌握 Rust 的引用和生命周期,你可以编写出既安全又高效的代码。练习 这些概念,逐步掌握 Rust 的内存管理系统。

暂无评论,快来抢沙发吧!