0


Rust生命周期,看这一篇就够了~

生命周期为什么要提出、是什么、怎么用

导航

一、生命周期为什么要提出

生命周期的主要作用避免悬垂引用,它会导致程序引用了本不该引用的数据

{let r;{let x =5;
        r =&x;}//x已失效println!("r: {}", r);//r此时引用了一个无效数据,称r为悬垂引用,报错}

二、生命周期是什么

通过生命周期的分析,确保所有权和借用的正确性,感觉可以说生命周期又是Rust本身的一种语言特性。。。。

三、生命周期怎么用

1、生命周期标注

生命周期标注通常是用在引用类型上的。

标注方式是‘+生命周期名称

(1)引用类型标注
&i32// 一个引用&'ai32// 具有显式生命周期的引用,生命周期名称是a&'amuti32// 具有显式生命周期的可变引用
(2)函数参数生命周期标注
fnuseless<'a>(first:&'ai32, second:&'ai32){}

函数名称后的<'a>只是生命周期名称声明,类似泛型也需要在函数名称后声明一样

上面的意思是,first参数和second参数的生命周期名称为‘a,这两个参数实际生命周期是大于等于’a

fnprint_refs<'a,'b>(x:&'ai32, y:&'bi32){println!("x is {} and y is {}", x, y);}

这段代码的意思是

print_refs

有两个引用参数,它们的生命周期

'a

'b

至少得跟函数活得一样久

(3)结构体字段中生命周期标注
#[derive(Debug)]structImportantExcerpt<'a>{
    part:&'astr,}fnmain(){let novel =String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");let i =ImportantExcerpt{
        part: first_sentence,};}

这段代码的意思是,结构体和结构体实例的生命周期为’a,结构体

ImportantExcerpt

所引用的字符串

str

生命周期需要大于等于该结构体的生命周期,否则报错

那你可能会问,字段引用怎么才会小于结构体的生命周期,那我们来看一个相关例子

#[derive(Debug)]structImportantExcerpt<'a>{
    part:&'astr,}fnmain(){let i;{let novel =String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");
        i =ImportantExcerpt{
            part: first_sentence,};}// String::from("Call me Ishmael. Some years ago...")已失效println!("{:?}",i);//i拥有的结构体中的字段指向失效的地址内容,也就是悬垂指针,报错}

此时就会报错

error[E0597]: `novel` does not live long enough
  --> src/main.rs:10:30
   |10|let first_sentence = novel.split('.').next().expect("Could not find a '.'");|                              ^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
14|}|     - `novel` dropped here while still borrowed
15|     println!("{:?}",i);|                     - borrow later used here
(4)方法中生命周期声明
impl<'a>ImportantExcerpt<'a>{fnannounce_and_return_part<'b>(&'aself, announcement:&'bstr)->&'bstrwhere'a:'b,//约束,b生命周期要小于a的生命周期{println!("Attention please: {}", announcement);self.part
    }}
(5)静态生命周期
let s:&'staticstr="我没啥优点,就是活得久,嘿嘿";
  1. 生命周期 'static 意味着能和程序活得一样久,例如字符串字面量和特征对象
  2. 实在遇到解决不了的生命周期标注问题,可以尝试 T:'static,有时候它会给你奇迹

2、赋值时生命周期规则

fnlongest<'a:'b,'b>(x:&'astr, y:&'bstr)->&'bstr{if x.len()> y.len(){
        x
    }else{
        y
    }}

在返回值有多个 不同生命周期 的值返回时,要求返回类型是 所有返回值中 生命周期最小的那个。
这里有一个生命周期赋值的基本原则

短命的才可以引用长命的,长命的引用短命的,当长命的想使用短命的,万一短命的已经挂了,那就发生悬挂引用现象,导致报错。

声明生命周期之间的长短关系,以上面的函数

longest

为例子,

'a:'b

表示为,在’a、'b两个生命周期中,**'b的生命周期小于’a的生命周期**。

那么在函数体内,无论返回x还是y,返回类型的生命周期至少都是小于或等于x和y的,满足借用安全,不会发生悬垂引用

3、生命周期消除

实际上,对于编译器来说,每一个引用类型都有一个生命周期,那么为什么我们在使用过程中,很多时候无需标注生命周期?例如

fnfirst_word(s:&str)->&str{let bytes = s.as_bytes();for(i,&item)in bytes.iter().enumerate(){if item ==b' '{return&s[0..i];}}&s[..]}

该函数的参数和返回值都是引用类型,尽管我们没有显式的为其标注生命周期,编译依然可以通过。其实原因不复杂,编译器为了简化用户的使用,运用了生命周期消除大法

减少了程序员的工作量

但是在生命周期消除不是万能的,需要注意的是

  1. 消除规则不是万能的,若编译器不能确定某件事是正确时,会直接判为不正确,那么你还是需要手动标注生命周期
  2. 函数或者方法中,输入参数的生命周期被称为 输入生命周期返回值的生命周期被称为 输出生命周期

然后生命周期消除主要遵循一下三个规则

  1. 每一个引用参数都会获得独自的生命周期

例如一个引用参数的函数就有一个生命周期标注: fn foo<'a>(x: &'a i32),两个引用参数的有两个生命周期标注:fn
foo<'a, 'b>(x: &'a i32, y: &'b i32), 依此类推

  1. 若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期,也就是所有返回值的生命周期都等于该输入生命周期

例如函数 fn foo(x: &i32) -> &i32,x 参数的生命周期会被自动赋给返回值 &i32,因此该函数等同于 fn
foo<'a>(x: &'a i32) -> &'a i32

  1. 若存在多个输入生命周期,且其中一个是 &self 或 &mut self,则 &self 的生命周期被赋给所有的输出生命周期

拥有 &self 形式的参数,说明该函数是一个 方法,该规则让方法的使用便利度大幅提升。

四、特别案例

看一段代码

structInterface<'a>{
    manager:&'amutManager<'a>}impl<'a>Interface<'a>{pubfnnoop(self){println!("interface consumed");}}structManager<'a>{
    text:&'astr}structList<'a>{
    manager:Manager<'a>,}impl<'a>List<'a>{pubfnget_interface(&'amutself)->Interface{//Interface生命周期和List实例生命周期相同Interface{
            manager:&mutself.manager
        }}}fnmain(){letmut list =List{
        manager:Manager{
            text:"hello"}};

    list.get_interface().noop();println!("Interface should be dropped here and the borrow released");// 下面的调用会失败,因为同时有不可变/可变借用// 但是Interface在之前调用完成后就应该被释放了use_list(&list);}fnuse_list(list:&List){println!("{}", list.manager.text);}

发生错误

error[E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable // `list`无法被借用,因为已经被可变借用
  --> src/main.rs:40:14
   |34|     list.get_interface().noop();|     ---- mutable borrow occurs here // 可变借用发生在这里
...
40|     use_list(&list);|              ^^^^^
   |||              immutable borrow occurs here // 新的不可变借用发生在这
   |              mutable borrow later used here // 可变借用在这里结束

这是因为Interface生命周期List实例生命周期相同

pubfnget_interface(&'amutself)->Interface{//Interface生命周期和List实例生命周期相同Interface{
            manager:&mutself.manager
        }}

然后主函数后面

list.get_interface().noop();//interface还没有dropprintln!("Interface should be dropped here and the borrow released");use_list(&list);//这句之后interface才drop,但是已经同时出现可变引用和不可变引用

那我们可以这样改

structInterface<'b,'a:'b>{
    manager:&'bmutManager<'a>}impl<'b,'a:'b>Interface<'b,'a>{pubfnnoop(self){println!("interface consumed");}}structManager<'a>{
    text:&'astr}structList<'a>{
    manager:Manager<'a>,}impl<'a>List<'a>{pubfnget_interface<'b>(&'bmutself)->Interface<'b,'a>where'a:'b{Interface{
            manager:&mutself.manager
        }}}fnmain(){letmut list =List{
        manager:Manager{
            text:"hello"}};

    list.get_interface().noop();println!("Interface should be dropped here and the borrow released");// 下面的调用可以通过,因为Interface的生命周期不需要跟list一样长use_list(&list);}fnuse_list(list:&List){println!("{}", list.manager.text);}
作者给出修改后的代码
impl<'a>List<'a>{pubfnget_interface<'b>(&'bmutself)->Interface<'b,'a>where'a:'b{Interface{
            manager:&mutself.manager
        }}}
因为 'a:'b,调用此方法的引用和interface生命周期的长度是一样的,当主函数的
list.get_interface().noop();
结束后
interface其实是可以死(可以比list的生命周期短)的,所以关于manager:&mutself.manager的可变引用也是可以死了的,所以不会后续的不可变引用冲突

本文转载自: https://blog.csdn.net/vince1998/article/details/138324413
版权归原作者 赛尔号副船长 所有, 如有侵权,请联系我们删除。

“Rust生命周期,看这一篇就够了~”的评论:

还没有评论