Rust生命周期
程序中每个变量都有一个固定的作用域,当超出变量的作用域以后,变量就会被销毁。变量在作用域中从初始化到销毁的整个过程称之为生命周期。
fn main() {let a; // --------------+-- a start{ // |let b = 5; // -+-- b start |} // -+-- b over |} // --------------+-- a over
main函数本身的作用域,另外一个是在main函数中使用一对{}定义了一个内部作用域。第2行代码声明了变量a,它的作用域是整个main函数,也可以说它的生命周期是从第2行代码到第6行代码。在第4行代码中声明了变量b,它的作用域是第4行到第6行。我们可以发现变量的生命周期是有长短的。rust中的借用是指对一块内存空间的引用。rust有一条借用规则是借用方的生命周期不能比出借方的生命周期还要长。
fn main() {let a; // -------------+-- a start{ // |let b = 5; // -+-- b start |a = &b; // | |} // -+-- b over |println!("a: {}", a); // |} // -------------+-- a over
变量b借给了变量a,所以a是借用方,b是出借方。可以发现变量a(借用方)的生命周期比变量b(出借方)的生命周期长,于是这样做违背了rust的借用规则(借用方的生命周期不能比出借方的生命周期还要长)。因为当b在生命周期结束时,a还是保持了对b的借用,就会导致a所指向的那块内存空间已经被释放了,那么变量a就会是一个悬垂引用。error[E0597]: `b` does not live long enough--> src/main.rs:5:13|5 | a = &b;| ^^ borrowed value does not live long enough6 | };| - `b` dropped here while still borrowed7 | println!("a:{}", a);| - borrow later used here
fn main() {let a = 1; // -------------+-- a startlet b = &a; // -------------+-- b startprintln!("a: {}", a); // |} // -------------+-- b, a over
对于一个参数和返回值都包含引用的函数而言,该函数的参数是出借方,函数返回值所绑定到的那个变量就是借用方。所以这种函数也需要满足借用规则(借用方的生命周期不能比出借方的生命周期还要长)。那么就需要对函数返回值的生命周期进行标注,告知编译器函数返回值的生命周期信息。
i32的引用类型,返回大的那个数的引用。fn max_num(x: &i32, y: &i32) -> &i32 {if x > y {&x} else {&y}}fn main() {let x = 1; // -------------+-- x startlet max; // -------------+-- max start{ // |let y = 8; // -------------+-- y startmax = max_num(&x, &y); // |} // -------------+-- y overprintln!("max: {}", max); // |} // -------------+-- max, x over
max_num函数返回的引用生命周期是什么,所以运行报错:error[E0106]: missing lifetime specifier--> src/main.rs:1:33|1 | fn max_num(x: &i32, y: &i32) -> &i32 {| ---- ---- ^ expected named lifetime parameter|= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `
函数的生命周期参数声明在函数名后的尖括号<>里,然后每个参数名跟在一个单引号'后面,多个参数用逗号隔开。如果在参数和返回值的地方需要使用生命周期进行标注时,只需要在&符号后面加上一个单引号'和之前声明的参数名即可。生命周期参数名可以是任意合法的名称。例如:
fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {if x > y {&x} else {&y}}fn main() {let x = 1; // -------------+-- x startlet max; // -------------+-- max start{ // |let y = 8; // -------------+-- y startmax = max_num(&x, &y); // |} // -------------+-- y overprintln!("max: {}", max); // |} // -------------+-- max, x over
上面代码对函数max_num的参数和返回值的生命周期进行了标注,用于告诉编译器函数参数和函数返回值的生命周期一样长。在第13行代码对max_num进行调用时,编译器会把变量x的生命周期和变量y的生命周期与max_num函数的生命周期参数'a建立关联。这里值得注意的是,变量x和变量y的生命周期长短其实是不一样的,那么关联到max_num函数的生命周期参数'a的长度是多少呢?实际上编译器会取变量x的生命周期和变量y的生命周期重叠的部分,也就是取最短的那个变量的生命周期与'a建立关联。这里最短的生命周期是变量y,所以'a关联的生命周期就是变量y的生命周期。
error[E0597]: `y` does not live long enough--> src/main.rs:13:27|13 | max = max_num(&x, &y);| ^^ borrowed value does not live long enough14 | }| - `y` dropped here while still borrowed15 | println!("max: {}", max);| --- borrow later used here
max_num函数返回值所绑定到的那个变量max(借用方)的生命周期是从第10行代码到第16行代码,而max_num函数的返回值(出借方)的生命周期是'a,'a的生命周期又是变量x的生命周期和变量y的生命周期中最短的那个,也就是变量y的生命周期。变量y的生命周期是代码的第12行到第14行。所以这里不满足借用规则(借用方的生命周期不能比出借方的生命周期还要长)。也就是为什么编译器会说变量y的生命周期不够长的原因了。函数的生命周期参数并不会改变生命周期的长短,只是用于编译来判断是否满足借用规则。fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {if x > y {&x} else {&y}}fn main() {let x = 1; // -------------+-- x startlet y = 8; // -------------+-- y startlet max = max_num(&x, &y); // -------------+-- max startprintln!("max: {}", max); // |} // -------------+-- max, y, x over
函数存在多个生命周期参数时,需要标注各个参数之间的关系。例如:
fn max_num<'a, 'b: 'a>(x: &'a i32, y: &'b i32) -> &'a i32 {if x > y {&x} else {&y}}fn main() {let x = 1; // -------------+-- x startlet y = 8; // -------------+-- y startlet max = max_num(&x, &y); // -------------+-- max startprintln!("max: {}", max); // |} // -------------+-- max, y, x over
'b: 'a来标注'a与'b之间的生命周期关系,它表示'a的生命周期不能超过'b,即函数返回值的生命周期'a(借用方)不能超过'b``(出借方),'a也不会超过'a`(出借方)。一个包含 引用成员的结构体,必须保证结构体本身的生命周期不能超过任何一个引用成员的生命周期。否则就会出现成员已经被销毁之后,结构体还保持对那个成员的引用就会产生悬垂引用。所以这依旧是rust的借用规则即借用方(结构体本身)的生命周期不能比出借方(结构体中的引用成员)的生命周期还要长。因此就需要在声明结构体的同时也声明生命周期参数,同时对结构体的引用成员进行生命周期参数标注。
<>里,每个参数名跟在一个单引号'后面,多个参数用逗号隔开。在进行标注时,只需要在引用成员的&符号后面加上一个单引号'和之前声明的参数名即可。生命周期参数名可以是任意合法的名称。例如:struct Foo<'a> {v: &'a i32}
Foo的生命周期与成员v的生命周期建立一个关联用于编译器进行借用规则判断。#[derive(Debug)]struct Foo<'a> {v: &'a i32}fn main() {let foo; // -------------+-- foo start{ // |let v = 123; // -------------+-- v startfoo = Foo { // |v: &v // |} // |} // -------------+-- v overprintln!("foo: {:?}", foo); // |} // -------------+-- foo over
上面代码的第14行到15行foo的生命周期依然没有结束,但是它所引用的变量v已经被销毁了,因此出现了悬垂引用。编译器会给出报错提示:变量v的的生命周期不够长。
有一个特殊的生命周期参数叫 static,它的生命周期是整个应用程序。跟其他生命周期参数不同的是,它是表示一个具体的生命周期长度,而不是泛指。static生命周期的变量存储在静态段中。
'static 生命周期,例如:let s: &'static str = "codercat is a static lifetime.";
let s: &str = "codercat is a static lifetime.";
static变量的生命周期也是'static。static V: i32 = 123;
fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {if x > y {&x} else {&y}}fn main() {let x = 1; // -------------+-- x startlet max; // -------------+-- max start{ // |static Y: i32 = 8; // -------------+-- Y startmax = max_num(&x, &Y); // |} // |println!("max: {}", max); // |} // -------------+-- max, Y, x over
max_num函数。在代码的第12行定义了一个静态变量,它的生命周期是'static 。max_num函数的生命周期参数'a会取变量x的生命周期和变量Y的生命周期重叠的部分。所以传入max_num函数并不会报错。原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。