首页 > 其他分享 >Rust生命周期,看这一篇就够了~

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

时间:2024-07-19 16:10:38浏览次数:12  
标签:生命周期 self list 就够 manager fn Interface Rust

https://blog.csdn.net/vince1998/article/details/138324413

生命周期为什么要提出、是什么、怎么用
导航
生命周期为什么要提出、是什么、怎么用
一、生命周期为什么要提出
二、生命周期是什么
三、生命周期怎么用
1、生命周期标注
(1)引用类型标注
(2)函数参数生命周期标注
(3)结构体字段中生命周期标注
(4)方法中生命周期声明
(5)静态生命周期
2、赋值时生命周期规则
3、生命周期消除
四、特别案例
一、生命周期为什么要提出
生命周期的主要作用是避免悬垂引用,它会导致程序引用了本不该引用的数据

{
    let r;

    {
        let x = 5;
        r = &x;
    }//x已失效

    println!("r: {}", r);//r此时引用了一个无效数据,称r为悬垂引用,报错
}
1
2
3
4
5
6
7
8
9
10
二、生命周期是什么
通过生命周期的分析,确保所有权和借用的正确性,感觉可以说生命周期又是Rust本身的一种语言特性。。。。

三、生命周期怎么用
1、生命周期标注
生命周期标注通常是用在引用类型上的。

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

(1)引用类型标注
&i32        // 一个引用
&'a i32     // 具有显式生命周期的引用,生命周期名称是a
&'a mut i32 // 具有显式生命周期的可变引用
1
2
3
(2)函数参数生命周期标注
fn useless<'a>(first: &'a i32, second: &'a i32) {}
1
函数名称后的<'a>只是生命周期名称声明,类似泛型也需要在函数名称后声明一样

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

fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}
1
2
3
这段代码的意思是print_refs 有两个引用参数,它们的生命周期 'a 和 'b 至少得跟函数活得一样久

(3)结构体字段中生命周期标注
#[derive(Debug)]
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    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,
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
这段代码的意思是,结构体和结构体实例的生命周期为’a,结构体ImportantExcerpt 所引用的字符串 str 生命周期需要大于等于该结构体的生命周期,否则报错。

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

#[derive(Debug)]
struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    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拥有的结构体中的字段指向失效的地址内容,也就是悬垂指针,报错
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
此时就会报错

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

1
2
3
4
5
6
7
8
9
10
11
(4)方法中生命周期声明
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str
    where
        'a: 'b,//约束,b生命周期要小于a的生命周期
    {
        println!("Attention please: {}", announcement);
        self.part
    }
}

1
2
3
4
5
6
7
8
9
10
(5)静态生命周期
let s: &'static str = "我没啥优点,就是活得久,嘿嘿";
1
生命周期 'static 意味着能和程序活得一样久,例如字符串字面量和特征对象
实在遇到解决不了的生命周期标注问题,可以尝试 T:'static,有时候它会给你奇迹
2、赋值时生命周期规则
fn longest<'a:'b,'b>(x: &'a str, y: &'b str) -> &'b str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
1
2
3
4
5
6
7
在返回值有多个 不同生命周期 的值返回时,要求返回类型是 所有返回值中 生命周期最小的那个。
这里有一个生命周期赋值的基本原则

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

声明生命周期之间的长短关系,以上面的函数longest为例子,'a:'b表示为,在’a、'b两个生命周期中,'b的生命周期小于’a的生命周期。

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

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

fn first_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
3
4
5
6
7
8
9
10
11
该函数的参数和返回值都是引用类型,尽管我们没有显式的为其标注生命周期,编译依然可以通过。其实原因不复杂,编译器为了简化用户的使用,运用了生命周期消除大法。

减少了程序员的工作量

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

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

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

若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期,也就是所有返回值的生命周期都等于该输入生命周期
例如函数 fn foo(x: &i32) -> &i32,x 参数的生命周期会被自动赋给返回值 &i32,因此该函数等同于 fn
foo<'a>(x: &'a i32) -> &'a i32

若存在多个输入生命周期,且其中一个是 &self 或 &mut self,则 &self 的生命周期被赋给所有的输出生命周期
拥有 &self 形式的参数,说明该函数是一个 方法,该规则让方法的使用便利度大幅提升。

四、特别案例
看一段代码

struct Interface<'a> {
    manager: &'a mut Manager<'a>
}

impl<'a> Interface<'a> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

struct Manager<'a> {
    text: &'a str
}

struct List<'a> {
    manager: Manager<'a>,
}

impl<'a> List<'a> {
    pub fn get_interface(&'a mut self) -> Interface {//Interface生命周期和List实例生命周期相同
        Interface {
            manager: &mut self.manager
        }
    }
}

fn main() {
    let mut list = List {
        manager: Manager {
            text: "hello"
        }
    };

    list.get_interface().noop();

    println!("Interface should be dropped here and the borrow released");

    // 下面的调用会失败,因为同时有不可变/可变借用
    // 但是Interface在之前调用完成后就应该被释放了
    use_list(&list);
}

fn use_list(list: &List) {
    println!("{}", list.manager.text);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
发生错误

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 // 可变借用在这里结束

1
2
3
4
5
6
7
8
9
10
11
12
这是因为Interface生命周期和List实例生命周期相同

  pub fn get_interface(&'a mut self) -> Interface {//Interface生命周期和List实例生命周期相同
        Interface {
            manager: &mut self.manager
        }
    }
1
2
3
4
5
然后主函数后面

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

struct Interface<'b, 'a: 'b> {
    manager: &'b mut Manager<'a>
}

impl<'b, 'a: 'b> Interface<'b, 'a> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

struct Manager<'a> {
    text: &'a str
}

struct List<'a> {
    manager: Manager<'a>,
}

impl<'a> List<'a> {
    pub fn get_interface<'b>(&'b mut self) -> Interface<'b, 'a>
    where 'a: 'b {
        Interface {
            manager: &mut self.manager
        }
    }
}

fn main() {

    let mut 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);
}

fn use_list(list: &List) {
    println!("{}", list.manager.text);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
作者给出修改后的代码
impl<'a> List<'a> {
    pub fn get_interface<'b>(&'b mut self) -> Interface<'b, 'a>
    where 'a: 'b {
        Interface {
            manager: &mut self.manager
        }
    }
}
因为 'a: 'b,调用此方法的引用和interface生命周期的长度是一样的,当主函数的
list.get_interface().noop();
结束后
interface其实是可以死(可以比list的生命周期短)的,所以关于manager: &mut self.manager的可变引用也是可以死了的,所以不会后续的
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/vince1998/article/details/138324413

标签:生命周期,self,list,就够,manager,fn,Interface,Rust
From: https://www.cnblogs.com/dhcn/p/18311668

相关文章

  • gin的生命周期——源码学习
    目录结构先来了解下其目录结构:.├──binding依据HTTP请求Accept解析响应数据格式│├──binding.go│├──......├──ginS├──internal├──render依据解析的HTTP请求Accept响应格式生成响应│├──data.go│├──html.go│......
  • 学Vue3 看这篇就够了
    Vue3简介Vue3是一个流行的开源JavaScript框架,用于构建用户界面和单页面应用。它带来了许多新特性和改进,包括更好的性能、更小的打包大小、更好的TypeScript支持、全新的组合式API,以及一些新的内置组件。1.Vue3的新特性Vue3引入了许多新特性,包括:组合式API:这是Vue3......
  • 【时时三省】(C语言基础)变量的作用域和生命周期
    山不在高,有仙则名。水不在深,有龙则灵。              ——csdn时时三省作用域局部变量的作用域:就是变量所在的局部范围 比如inta=0它只能在它的那个括号里面使用出了括号就没用了全局变量的作用域:整个工程整个工程里面就是在哪里都可以......
  • 暑假两个月学习AI产品经理详细路线,看这一篇就够了
    以下是一个暑假期间学习AI产品经理的详细路线,分为八个周来进行:第1周:了解AI产品管理基础阅读材料:《人工智能:一种现代的方法》了解AI基础。《人人都是产品经理》了解产品管理基础。在线课程:Coursera上的“人工智能基础”课程。edX上的“产品管理基础”课程。实践:调研......
  • Vue的八大生命周期
    生命周期组件从开始创建、运行、销毁的过程中会伴随各种各样的事件,这些事件统称为生命周期1、beforeCreate周期创建之前:一开始就可以执行。data和methods中的数据还没有初始化,获取不到data和methods里面的数据作用:页面重定向2、created周期创建之后第一个可以操作data......
  • C++基础-引用详解(全网最详细,看这篇就够了)
    目录前言一、引用的概念二、引用的特性三、常引用四、引用的使用场景4.1引用做参数4.2引用做返回值五、传值、传引用效率比较5.1值和引用的作为返回值类型的性能比较5.2值和引用作为参数传递之间的性能差别六、引用和指针的区别结束语前言本节我们正式进入C++......
  • 自学网络安全入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    如何入门网络安全_网络安全自学由于我之前写了不少网络安全技术相关的故事文章,不少读者朋友知道我是从事网络安全相关的工作,于是经常有人在微信里问我:我刚入门网络安全,该怎么学?要学哪些东西?有哪些方向?怎么选?不同于Java、C/C++等后端开发岗位有非常明晰的学习路线,网路安......
  • 自学网络安全入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    如何入门网络安全_网络安全自学由于我之前写了不少网络安全技术相关的故事文章,不少读者朋友知道我是从事网络安全相关的工作,于是经常有人在微信里问我:我刚入门网络安全,该怎么学?要学哪些东西?有哪些方向?怎么选?不同于Java、C/C++等后端开发岗位有非常明晰的学习路线,网路安......
  • 【2024版】超详细Python+Pycharm安装保姆级教程,Python+Pycharm环境配置和使用指南,看完
    目录一、Python开发环境配置1.Python下载与安装二、PyCharm安装+运行测试+汉化1.PyCharm下载及安装2.解释器配置及项目测试3.PyCharm汉化本文将从Python解释器安装到Pycharm专业版安装和配置汉化等使用都进行了详细介绍,希望能够帮助到大家。Python解释器&Pycharm安......
  • 营销广告电商行业如何实现降本提效?看这篇就够了!
    最新技术资源(建议收藏)https://www.grapecity.com.cn/resources/“通过嵌入葡萄城的SpreadJS表格控件,节省了70%整理报表的时间,降低了90%数据下载更新与整理的时间,帮助企业提升运营人效、精细化运营,实现品效双升。”——北京桔柚科技有限公司公司简介北京桔柚科技有......