核心概念
借用和生命周期
Borrowing & Borrow Checker & Lifetime
Borrowing
引用和借用有两种描述。
引用(Reference):
- 引用是一种变量的别名,通过
&符号来创建。(非所有权) - 引用可以是不可变的(
&T)或可变的(&mut T) - 引用允许在不传递所有权的情况下访问数据,它们是安全且低开销的。
借用(Borrowing):
- 借用是通过引用(Reference)来借用(Borrow)数据,从而在一段时间内访问数据而不拥有它。
- 借用分为可变借用和不可变借用。可变借用(
&mut)允许修改数据,但在生命周期内只能有一个可变借用。不可变借用(&)允许多个同时存在,但不允许修改数据。
提示
引用更看重对象,借用更看重行为。
Borrow Checker
Borrow Checker的规则有以下:
- 不可变引用规则:在任何给定的时间,要么有一个可变引用,要么有多个不可变引用,但不能同时存在可变引用和不可变引用。这确保了在同一个时间只有一个地方对数据进行修改,或者有多个地方同时读取数据。
- 可变引用规则:在任何给定的时间,只能有一个可变引用来访问数据。这防止了并发修改相同数据的问题,从而防止数据竞争。
- 生命周期规则:引用的生命周期必须在被引用的数据有效的时间范围内。这防止了悬垂引用,即引用的数据已经被销毁,但引用仍然存在。
- 可变引用与不可变引用不互斥:可以同时存在多个不可变引用,因为不可变引用不会修改数据,不会影响到其他引用。但不可变引用与可变引用之间是互斥的。
Define Lifetime Manually
生命周期参数在函数/结构体签名中指定:
一般情况下Borrow Checker会自行推断,在函数/结构体签名中使用生命周期参数允许函数声明引用的有效范围。
例子:
1fn main() {2 let mut s = String::from("Hello");3 // 不可变引用,可以有多个不可变引用4 let r1 = &s;5 let r2 = &s;6 println!("{} {}", r1, r2); // Hello Hello78 // 可变引用只能有一个9 let r3 = &mut s;10 println!("{}", r3); // Hello1112 let result: &str;13 {14 // result = "ff";15 let r4 = &s; // 给r4定义,r4的生命周期结束16 result = ff(r4); // 相当于给result初始化,result的生命周期还没结束17 }1819 // println!("r4 {}", r4); // r4的所有权已经转移给result20 println!("{}", result); // Hello21}2223fn ff<'a>(s: &'a str) -> &'a str {24 s25}
Lifetime & Functions
Any Reference has a Lifetime
大多数情况下,生命周期是隐式且被推断的。
生命周期的主要目的是防止悬垂引用。
关于“悬垂引用”的概念是指,引用指向的数据在代码结束后被释放,但引用仍然存在。
- 生命周期的引入有助于确保引用的有效性,防止程序在运行时出现悬垂引用的情况
- 通过生命周期的推断,Rust能够在编译时检查代码,确保引用的有效性,而不是在运行时出现悬垂引用的错误
Three Rules to infer Lifetime
编译器在没有显式注解的情况下,使用三个规则来推断这些生命周期:
- 第一个规则是每个作为引用的参数都会得到它自己的生命周期参数
- 第二个规则是,如果只有一个输入生命周期参数,那么该生命周期将被分配给所有输出生命周期参数(该生命周期将分配给返回值)
- 第三个规则是,如果有多个输入生命周期参数,但其中一个是对
self或不可变self的引用时。因为在这种情况下它是一个方法,所以self的生命周期被分配给所有输出生命参数
例子:
1// 字面量的生命周期都是一样的时候,性能相对慢一些2fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {3 if s1.len() > s2.len() {4 s15 } else {6 s27 }8}910// 返回的字面量的生命周期out是a和b的交集11fn longest_str<'a, 'b, 'out>(s1: &'a str, s2: &'b str) -> &'out str12where13 'a: 'out,14 'b: 'out,15{16 if s1.len() > s2.len() {17 s118 } else {19 s220 }21}222324fn no_need(s: &'static str, s1: &str) -> &'static str {25 s26}2728fn main() {29 println!("longest s: {}", longest("aabb", "ab")); // longest s: aabb30 let result: &str;31 {32 let r2 = "cba";33 result = longest_str("aabbcc", r2);34 println!("longest s of out: {}", result); // longest s of out: aabbcc35 }36 println!("no need {}", no_need("hh", "")); // no need hh37}
Lifetime & Struct
Reference in Struct
- 在结构体中的引用需要标注生命周期
- 结构体的方法(
self等)不需要标注生命周期
例子:
1struct MyString<'a> {2 text: &'a str, // 最好写String3}45impl<'a> MyString<'a> {6 fn get_length(&self) -> usize {7 self.text.len()8 }910 fn modify_data(&mut self) {11 self.text = "world";12 }13}1415struct StringHolder {16 data: String,17}1819impl StringHolder {20 fn get_length(&self) -> usize {21 self.data.len()22 }2324 fn get_ref<'a>( &'a self) -> &'a String {25 &self.data26 }27}2829fn main() {30 let str1 = String::from("value");31 let mut x = MyString{32 text: str1.as_str(),33 };34 println!("x before mod: {}", x.text); // x before mod: value35 x.modify_data();36 println!("x after mod: {}", x.text); // x after mod: world3738 let holder = StringHolder{39 data: String::from("Hello"),40 };41 println!("{}", holder.get_ref()); // Hello4243}