mirror of
https://github.com/RustyCab/LearnRustEasy.git
synced 2026-02-04 18:53:28 +08:00
Update chapter_3_11.md
This commit is contained in:
@@ -1,3 +1,210 @@
|
||||
# 生命周期
|
||||
# 3.11 生命周期
|
||||
Rust中的生命周期是用来管理内存的一种机制。在Rust中,内存的所有权和使用必须是确定的,生命周期就是用来确定内存的使用范围的(说的更具体点,就是确定引用的有效范围):
|
||||
- 编译器大多数时间能够自动推导生命周期(3.10.1和3.10.2中的例子编译器都能自动推导);
|
||||
- 在多种类型存在,且编译器无法推导某个引用的生命周期时,需要在代码中显式的标明生命周期。
|
||||
|
||||
todo
|
||||
## 3.11.1 悬垂指针和生命周期
|
||||
生命周期的主要目的就是为了避免悬垂引用。考虑如下代码:
|
||||
```Rust
|
||||
fn main() {
|
||||
let r;
|
||||
{
|
||||
let x = 5;
|
||||
r = &x;
|
||||
}
|
||||
println!("r = {}", r); //r为悬垂引用
|
||||
}
|
||||
```
|
||||
上面代码中,x的有效作用域是从第4行到第6行花括号结束前,即从第6行的花括号后开始,x变为无效,r为x的应用,此时将指向的是一块无效的内存。其内存示意图如下:
|
||||
|
||||

|
||||
|
||||
## 3.11.2 借用检查
|
||||
Rust编译器有一个借用检查器,用它来检查所有的应用的都是有效的,具体的方式为比较变量及其引用的作用域。
|
||||
### 1. 示例1如下:
|
||||
```Rust
|
||||
fn main() {
|
||||
let r; //------------------------+-------'a
|
||||
{ // |
|
||||
// |
|
||||
let x = 5; //----+---'b |
|
||||
r = &x; // | |
|
||||
} //----+ |
|
||||
println!("r = {}", r); // | //r为悬垂引用
|
||||
} //---------------------------------------+
|
||||
```
|
||||
对于上面的代码,借用检查器将r的生命周期标注为'a,将x的生命周期标注为'b,然后比较'a和'b的范围,发现'b < 'a,被引用的对象比它的引用者存在的时间还短,然后编译报错。
|
||||
|
||||
### 2. 示例2如下:
|
||||
```Rust
|
||||
fn main() {
|
||||
let x = 5; // ----------+-- 'b
|
||||
// |
|
||||
let r = &x; // --+-- 'a |
|
||||
// | |
|
||||
println!("r: {}", r); // | |
|
||||
// --+ |
|
||||
} // ----------+
|
||||
```
|
||||
对于上面的代码,借用检查器将x的生命周期标注为'b,将r的生命周期标注为'a,比较两者范围,发现'b > 'a,被引用对象比它的应用者存在的时间长,编译检查通过。
|
||||
|
||||
## 3.11.3 编译器有时无法自动推导生命周期
|
||||
|
||||
如下代码会报错:
|
||||
|
||||
```Rust
|
||||
fn longest(x: &str, y: &str) -> &str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
let s1 = String::from("abcd");
|
||||
let s2 = String::from("ad");
|
||||
let r = longest(s1.as_str(), s2.as_str());
|
||||
}
|
||||
```
|
||||
原因为:***在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要程序员在代码中手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析***。
|
||||
|
||||
## 3.11.4 标注生命周期
|
||||
### 1. 生命周期标注的语法
|
||||
|
||||
在开始说明生命周期标注语法前,需要特别明确的是:生命周期标注并不会改变任何引用的实际作用域,标记的生命周期只是为了取悦编译器,让编译器不要难为代码。
|
||||
生命周期标注的语法为:生命周期参数名称必须以撇号'开头,其名称通常全是小写,类似于泛型,其名称非常短。比较常见的是使用'a作为第一个生命周期标注。生命周期参数标注位于引用符号&之后,并有一个空格来将引用类型与生命周期注解分隔开。
|
||||
|
||||
下面为生命周期标注的例子:
|
||||
```Rust
|
||||
&i32 // 引用
|
||||
&'a i32 // 带有显式生命周期的引用
|
||||
&'a mut i32 // 带有显式生命周期的可变引用
|
||||
```
|
||||
|
||||
### 2. 函数签名中的生命周期
|
||||
对于3.11.3例子中的函数,显式标注生命周期后的代码如下:
|
||||
```Rust
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
```
|
||||
上面代码中,对x和y标注生命周期为'a,返回的引用的生命周期也为'a。当调用这个函数时,就要求传入的x和y的生命周期必须是大于等于'a的。当不遵守这个规则的参数传入时,借用检查器就会报错。
|
||||
|
||||
### 3. 深入思考生命周期标注
|
||||
(1)指定生命周期参数的正确方式依赖函数实现的具体功能。如下代码中将不用标注y的生命周期,因为返回值不依赖于y的生命周期。
|
||||
```Rust
|
||||
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
|
||||
x
|
||||
}
|
||||
```
|
||||
|
||||
(2)当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用没有指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用。如下代码将编译错误:
|
||||
```Rust
|
||||
fn longest<'a>(x: &str, y: &str) -> &'a str {
|
||||
let result = String::from("really long string");
|
||||
result.as_str() //将产生悬垂引用,result在花括号前“}”离开作用域
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 结构体中的生命周期
|
||||
结构体中的生命周期标注示例如下:
|
||||
```Rust
|
||||
#[derive(Debug)]
|
||||
struct A<'a> {
|
||||
name: &'a str, // 标注生命周期
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let n = String::from("andy");
|
||||
let a = A { name: &n };
|
||||
println!("{:#?}", a);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 生命周期省略
|
||||
在大多数情况下,程序员不用在代码中显式标注生命周期,因为编译器能自动推导。不标注生命周期,我们称之为生命周期省略。例如下面的代码可以正确编译:
|
||||
```Rust
|
||||
fn get_s(s: &str) -> &str {
|
||||
s
|
||||
}
|
||||
```
|
||||
关于生命周期省略有如下说明:
|
||||
|
||||
(1)遵守生命周期省略规则的情况下能明确变量的生命周期,则无需明确指定生命周期。函数或者方法的参数的生命周期称为输入生命周期,而返回值的生命周期称为输出生命周期。
|
||||
|
||||
(2)编译器采用三条规则判断引用何时不需要生命周期标注,当编译器检查完这三条规则后仍然不能计算出引用的生命周期,则会停止并生成错误。
|
||||
|
||||
(3)生命周期标注省略规则适用于fn定义以及impl块定义。
|
||||
|
||||
三条判断规则如下:
|
||||
|
||||
a、每个引用的参数都有它自己的生命周期参数。例如如下:
|
||||
一个引用参数的函数,其中有一个生命周期: ```fn foo<'a>(x: &'a i32)```
|
||||
两个引用参数的函数,则有两个生命周期 :```fn foo<'a, 'b>(x: &'a i32, y: &'b i32)```
|
||||
|
||||
以此类推。
|
||||
|
||||
b、如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:
|
||||
```Rust
|
||||
fn foo(x: &i32) -> &i32 等价于 fn foo<'a>(x: &'a i32) -> &'a i32
|
||||
```
|
||||
c、如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故为```&self```或者```&mut self```,那么```self```的生命周期被赋予所有输出生命周期参数。
|
||||
|
||||
### 6. 方法中的生命周期
|
||||
结构体字段的生命周期必须总是在impl关键字之后声明并在结构体名称之后使用,这些声明周期是结构体类型的一部分,示例如下:
|
||||
```Rust
|
||||
struct StuA<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
impl<'a> StuA<'a> {
|
||||
fn do_something(&self) -> i32 {
|
||||
3
|
||||
}
|
||||
}
|
||||
```
|
||||
下面的例子中,其方法没有显式标注生命周期,因为它符合生命周期省略规则中的第三条规则,代码如下:
|
||||
```Rust
|
||||
struct StuA<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
impl<'a> StuA<'a> {
|
||||
fn do_something2(&self, s: &str) -> &str {
|
||||
self.name //此处符合声明周期注解省略的第三条规则
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 静态生命周期
|
||||
静态生命周期定义方式为:```'static```,其生命周期存活于整个程序期间。所有的字符字面值都拥有```'static```生命周期,代码中可以如下来标注:
|
||||
```Rust
|
||||
let s: &'static str = "I have a static filetime";
|
||||
```
|
||||
|
||||
### 8. 结合泛型参数、trait bounds和生命周期的例子
|
||||
下面示例为在同一函数中指定泛型类型参数、trait bounds 和生命周期:
|
||||
```Rust
|
||||
use std::fmt::Display;
|
||||
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
println!("Announcement! {}", ann);
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
let s1 = String::from("s1");
|
||||
let s2 = String::from("s2!");
|
||||
let ann = 128;
|
||||
let r = longest_with_an_announcement(s1.as_str(), s2.as_str(), ann);
|
||||
println!("r = {}", r);
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user