rust踩雷笔记3——生命周期的理解

这篇具有很好参考价值的文章主要介绍了rust踩雷笔记3——生命周期的理解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

生命周期是rust中最难的概念——鲁迅

这一块内容即便是看rust圣经,第一遍也有点懵。今天早上二刷突然有了更直观的认识,记录一下。

概念和基本使用

生命周期就是应用的有效作用域,它的主要作用是避免悬垂引用。

悬垂引用的典型例子:

{
    let y;
    {
        let x = 1;
        y = &x;
    }
    println!("y: {}", y);
}

简而言之就是y引用的变量在y被使用之前就释放了。

我们通过肉眼检查上面代码,会发现x的生命周期没有延续到y被使用的时候,所以会发现问题。rust为了安全做出了很多约束,所以这里即便是我们不用肉眼观察,也能发现问题。

一个例子彻底理解最基本的内容

让我们直接切入使用,来体会生命周期的用法。

首先,就算你什么都不做,编译器大多数可以自动推导出生命周期。

如果你要手动标注声明周期,那么就加'a符号,'是重点,a的话可以替换。

看如下代码:

let y;
{
    let x = 1;
    y = &x;
}
println!("y: {}", y);

很显然发生了悬垂引用,报错信息:
x does not live long enough, borrowed value does not live long enough
x的生命周期在花括号内,想要避免悬垂引用,需要将x的生命周期延长到y的位置

方法一✔️:

let x = 1;
let y;
{
    y = &x;
}
println!("y: {}", y);

这样肯定就没错了,x作为被引用的变量,生命周期比引用它的y长

方法二❎:
不改变代码,只是添加生命周期引用?

当然不行!

生命周期引用只是为了向编译器做出说明,如果加了'a的话,就说明引用的作用域大于等于’a。如果对多个引用加同一个’a,说明这些引用的作用域都大于等于’a。可以看到,这是一种约束条件;
生命周期标注’a并不改变引用的真实生命周期,只是告诉编译器,当不满足’a表示的约束条件时,就报错。

⭐️一个例子体会生命周期的作用——让编译器检查约束条件

let x = 5;
{
    let y = &x;
}

这个代码肯定没有问题,被引用的x比引用y活得久。

那如果此时我加入生命周期约束

let x = 5;
{
    let y: &'static i32 = &x;
}

这是在告诉编译器y具有全局生命周期,但实际上y的生命周期在花括号内,上述代码没有悬垂引用。

那你觉得此时,编译器报不报错?
当然会!记住两个原则:

  1. 生命周期标注不改变引用的真实生命周期(比如上述的y还是在花括号内)
  2. 编译器会相信你标注的生命周期,而不是引用真实的生命周期

没错,当你标注’static起,编译器就认为y可以活全局,那么此时它检查x和y的约束关系,就会按照这个来。所以报错信息是:
x does not live long enough, borrowed value does not live long enough
编译器觉得x不能活全局,所以y活全局的话,会发生悬垂引用。

⚠️注意:
问:上述代码有没有发生悬垂引用?
答:没有。
问:'static有没有改变y的真实生命周期?
答:没有。
问:那为啥报了悬垂引用的错误?
答:因为编译器相信你的标注,从而它认为y就是活全局的。

一个例子理解函数签名为什么要有生命周期标注

摘自rust圣经的一段代码:

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

这个程序会报错:
help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from x or y

我们来分析一下,当longest被调用的时候,x引用string1,y引用string2(严格讲string2本身是&str)。result因为获取返回值,所以是引用string1或者string2,那么问题来了,rust因为严格的安全检查,此时要检查是否有悬垂引用,即检查是否有被引用的变量生命周期大于引用变量。

可是编译期间是无法获知result会引用哪个字符串的,编译器这个大聪明就无法检查是否有悬垂引用。

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中生命周期的交集。所以相当于告诉了编译器,把具体引用传给返回值的时候,返回值的生命周期为x和y生命周期的交集。

如下例子深入理解:
(依旧摘自rust圣经)

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

分析:我们告诉编译器的结论是,'a的生命周期是string1和string2的交集,也就是string2的生命周期;因此result生命周期等于string2的生命周期。

然后,编译器根据我们的结论,去检查代码有没有悬垂引用的风险,虽然上述函数调用,会使得result引用的是string1,这样一看没有悬垂引用。但是编译期间不知道result会引用谁,此时编译器一看,如果引用的是string2,那么悬垂引用就会发生,所以就会报错。

由于此例子中result只会引用string1,所以代码也许可以这样改:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

就不会出错了。

先写这么多,剩下的结合实践深入了解。

我又回来了,因为发现一个更好做说明的例子:

⭐️能不能对编译器蒙混过关?

这个标题什么意思呢,我们再把rust圣经中一段代码摘过来

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

刚刚说了,这个生命周期标注会让编译器意识到有悬垂引用风险,于是拒绝代码执行

safety这个单词被发明出来前,还有个单词表示安全,叫rusty——鲁迅

这时候咱可能不爽了,你看明明string1更长,longest只会返回对string1的引用,肉眼就能发现没有悬垂引用,为什么编译器这个大聪明一定要去检查string2呢?

不行,不能惯着编译器,我们修改代码:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

我直接把y的’a标注删掉,看你怎么检查string2.

结果编译器也是倔强,反手来了个新错误:
explicit lifetime required in the type of y, lifetime 'a required
也就是说没有标注编译器就没办法检查悬垂引用,没办法检查就会直接报错。

那我继续改:

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

改成’b总可以了吧,还是不行:
lifetime may not live long enough, consider adding the following bound: 'b: 'a
'b: 'a表示’b生命周期大于’a。

那继续改:

fn longest<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

结果依旧是报错:
string2 does not live long enough

你会发现,

  • 不标注y生命周期的时候,编译器因为无法检查悬垂引用而报错;
  • 给y标注一个’b后,因为函数返回值是’a,编译器无法判断’b和’a的关系,也就无法检查悬垂引用;
  • 标注’a: 'b或者’b: 'a都可以表示两者关系,但是我们只能选没有悬垂引用的那种;
  • longest返回值是一个引用,它可能会引用y,所以这里只能是’b: 'a,如果你标注’a: 'b,说明你告诉编译器返回值获得比y久,那么编译器会相信你说的,就直接因为可能发生悬垂引用报错;
  • 那你正好标注’b: 'a,但是编译器发现和事实不符,于是报错。

⚠️总结:

  1. 你给编译器说什么,编译器就信什么,所以你不能乱说,要标注正确的生命周期,以及不同生命周期的关系。如果编译器从你的标注就感觉到了悬垂引用,就会报错;
  2. 你标注了什么,不代表真实的生命周期就是什么,你的标注只是告诉编译器你希望的事实,如果实际情况不满足标注的约束,编译器就会报错。

简版:
你的标注若有悬垂引用,哪怕实际上没有,编译器也会报错;
你的标注若与事实不符,标注没有悬垂引用而实际有或者有风险发生,编译器报错。

所以是否报错要看:
标注是否正确+事实是否符合正确的标注约束文章来源地址https://www.toymoban.com/news/detail-660030.html

到了这里,关于rust踩雷笔记3——生命周期的理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • rust踩雷笔记(1)——切片传参和解引用赋值

    最近学习rust,网上资料还是很有限,做题遇到的问题,有时需要自己试验。把自己做题过程遇到的问题,和试验的结论,做一些简单记录。 阅读下列文字和代码 用切片(的引用)做参数要非常小心,切片中的某个元素直接用=赋值,用的是copy方式而不是所有权转移(实践证

    2024年02月12日
    浏览(35)
  • 30天拿下Rust之生命周期

    概述         在Rust中,生命周期是一个非常重要的概念,是保证内存安全和防止悬垂引用的核心机制之一。通过精确地跟踪引用的生命周期,Rust能够在编译阶段就防止许多其他语言在运行时才会遇到的内存问题。在Rust中,生命周期代表了引用的有效时间段。当我们创建

    2024年03月20日
    浏览(46)
  • rust踩雷笔记(4)——刷点Vec相关的题(持续更新)

    俗话说,孰能生巧,今天是第六天接触Rust,感觉基础语法和特性没什么问题了(当然如果你整天都学这个可能2天半就够了),但是想达到熟练使用,还需要刷点题。算法我相信能来看rust博客的人都是大牛(只有我最菜),应该只有数据结构的困扰,所以接下来的博客会侧重

    2024年02月12日
    浏览(35)
  • Rust语法:所有权&引用&生命周期

    垃圾回收管理内存 Python,Java这类语言在管理内存时引用了一种叫做垃圾回收的技术,这种技术会为每个变量设置一个引用计数器(reference counter),来统计每个对象的引用次数。 一旦某个对象的引用数为0,垃圾回收器就会择取一个时机将其所占用的空间回收。 以Python为例子

    2024年02月12日
    浏览(55)
  • Rust之泛型、trait与生命周期

    泛型是具体类型或其他属性的抽象替代。在编写代码时,可以直接描述泛型的行为,或者它与其他泛型产生的联系,而无须知晓它在编译和运行代码时采用的具体类型。 们可以在声明函数签名或结构体等元素时使用泛型,并在随后搭配不同的具体类型来使用这些元素。 当使

    2024年02月13日
    浏览(39)
  • Rust踩雷笔记(5)——刷点链表的题(涉及智能指针Box,持续更新)

    只能说Rust链表题的画风和C++完全不一样,作为新手一时间还不太适应,于是单独为链表、智能指针开一篇,主要记录leetcode相关题型的答案以及注意事项。 🍑关键操作 as_ref() 将 OptionT 、 OptionT 或者 mut OptionT 转换为 OptionT as_mut() 将 OptionT 、 mut OptionT 转换为 Optionmut T ,不能对

    2024年02月11日
    浏览(34)
  • rust踩雷笔记(2)——一道hard带来的思考[哈希表、字符串、滑动窗口]

    今天被一道hard恶心坏了,算法不难,用C++几分钟的事。用rust主要还是缺乏对语言的熟练度,记录一下,主要的坑在下面这个操作: 对String取其中某个位置的char。 可能你会有疑问:这不是直接nth()取就行了么。没错,我看有些题解也是这样做的,但是那样会在某些字符长度很

    2024年02月12日
    浏览(36)
  • 文盘Rust -- 生命周期问题引发的 static hashmap 锁 | 京东云技术团队

    2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:https://github.com/jiashiwen/interactcli-rs。 春节以前看到axum已经0.4.x了,于是想看看能不能用rust做个服务端的框架。 春节后开始动手,在做的过程中会碰到各种有趣

    2024年02月09日
    浏览(39)
  • 【react从入门到精通】深入理解React生命周期

    ✍创作者:全栈弄潮儿 🏡 个人主页: 全栈弄潮儿的个人主页 🏙️ 个人社区,欢迎你的加入:全栈弄潮儿的个人社区 📙 专栏地址:react从入门到精通 【分享几个国内免费可用的ChatGPT镜像】 【10几个类ChatGPT国内AI大模型】 【用《文心一言》1分钟写一篇博客简直yyds】 【用

    2024年02月03日
    浏览(49)
  • Rust in Action笔记 第五章 深入理解数据

    如果希望看到f32类型的数转换成整型数字u32类型,需要在unsafe包裹下调用 std::mem::transmute(data) ,因为在安全的Rust语法中没有把整型数据按照bit转换成浮点数据的实现,如果想要看到浮点数的二进制输出(通过 {:b} ),需要先通过unsafe把浮点数转换成整型数再输出; 大端(b

    2024年02月09日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包