Rust与生命周期(一)

简介: Rust与生命周期(一)
生命周期可以说是Rust中最特别的性质,也是Rust中比较难(反人类)的部分。

生命周期(一)

关于生命周期,我们应该知道:

  • Rust的每个引用都有自己的生命周期
  • 生命周期:引用保持有效的作用域
  • 大多数情况:生命周期是隐式的、可被推断的
  • 当引用的生命周期可能以不同的方式互相关联时,我们需要手动标注生命周期

生命周期的目的

生命周期最主要的目的就是避免悬垂引用(dangling reference)

let r;
{
    let x = 5;
    r = &x; //`x` does not live long enough borrowed value does not live long enough
}
println!("r: {}", r);

上面会报错是因为在调用r的时候,x已经走出作用域,也就是被销毁了。而r被初始化为x的引用实际上就是一个悬垂引用

那么Rust是如何进行判断的呢?

借用检测器

Rust编译器的借用检查器:比较作用域来判断所有的借用是否合法。

image-20221218214749287

还是上面的例子,x的生命周期为b,r的生命周期为a。b的生命周期比a短。所以不合法。

如果要合法,那么x的生命周期至少不应该比r短。我们可以这样改动:

let x = 5;
let r = &x;

println!("r: {}", r);

函数中的泛型的生命周期

我们现在要写一个比较两个字符串长度的,并且返回长的那一个。逻辑很简单,我们可能会这样写:

fn main() {
    let s1 = "sss";
    let s2 = String::from("yesyesyes");
    println!("{}", longest(s1, s2.as_str()));
}

fn longest(s1: &str, s2: &str) -> &str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

但是却报错了,报错的原因是:此函数的返回类型包含借用的值,但签名没有说明它是从“S1”还是“S2”借用的。但是我们将其返回值为一个固定的,依然会报同样的错:

fn longest(s1: &str, s2: &str) -> &str {
    s2
}

所以这与函数体的逻辑无关,而是与函数签名有关。我们在这里就必须使用泛型的生命周期来编写代码:

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

生命周期的标注语法

生命周期的标注不会改变引用的生命周期长度

当指定了泛型生命周期参数,函数可以接收带有任何生命周期的引用

生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期

生命周期标注的语法

生命周期参数名:

  • 以单引号'开头
  • 通常全小写且非常短
  • 通常情况下使用'a

生命周期标注的位置:

  • 在引用的&符号后
  • 使用空格将标注和引用类型分开

&i32一个引用,&'a i32带有显式生命周期的引用,&'a mut i32带有显式生命周期的可变引用

单个生命周期标注本身没有意义,标注之所以存在,是为了向rust描述多个泛型生命周期参数间的关系

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

比如上面这个例子中就是说s1和s2这两个引用必须不能短于泛型生命周期'a

函数签名中的生命周期标注

泛型生命周期参数声明在:函数名和参数列表之间的<>里(泛型参数)

现在我们改动一下上面的例子:

fn main() {
    let s1 = String::from("yesyesyes");
    {
        let s2 = "sss";
        let result = longest(s1.as_str(), s2);
        println!("{}", result);
    }
}

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

这里依然能通过,s1->2-8,s2生命周期为静态的生命周期(static),在整个程序运行期间都存活

&str是直接在可执行文件中加载的,所以指这块内存的引用,一定会一直指向一个合法内存,所以其引用的生命周期是'static,也就是全局静态

我们再改动一下代码:

fn main() {
    let s1 = String::from("yesyesyes");
    let result;
    {
        let s2 = String::from("sss");
        result = longest(s1.as_str(), s2.as_str());
    }
    println!("{}", result);
}

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

我们将result声明在块外,println也在块外执行。到目前为止还没报错,但当我们将s2的类型转换为String类型的时候就报错了。

我们之前了解到:<'a>的生命周期是取的s1和s2中短的那一个。在这个例子中,'a的生命周期就是s2的生命周期,也就是5-7

使用cargo run来看看爆的是什么错误:

image-20221218222903172

我们看到,s2被借用了,但它的生命周期不够长。s2在离开作用域后也就是第7行后,在第8行依然被发生了借用,所以就报错了

我们只需要记住:生命周期'a的实际生命周期是:x和y两个生命周期中较小的那个

相关文章
|
2月前
|
Rust 安全 编译器
Rust中的生命周期与借用检查器:内存安全的守护神
本文深入探讨了Rust编程语言中生命周期与借用检查器的概念及其工作原理。Rust通过这些机制,在编译时确保了内存安全,避免了数据竞争和悬挂指针等常见问题。我们将详细解释生命周期如何管理数据的存活期,以及借用检查器如何确保数据的独占或共享访问,从而在不牺牲性能的前提下,为开发者提供了强大的内存安全保障。
|
3月前
|
存储 Rust 安全
Rust 笔记:Rust 语言中的 所有权 与 生命周期
Rust 笔记:Rust 语言中的 所有权 与 生命周期
131 0
|
2月前
|
Rust 安全 编译器
Rust中避免常见错误:悬挂引用与生命周期不匹配
本文深入探讨了Rust编程语言中常见的两个内存管理错误:悬挂引用和生命周期不匹配,并提供了避免这些错误的实用方法。我们将详细解释这两种错误的来源,并通过示例展示如何在Rust中通过正确的生命周期标注和借用规则来避免它们,从而确保代码的内存安全性。
|
Rust 安全 Java
Rust学习笔记之泛型、trait 与生命周期
1. 泛型大补汤 推荐阅读指数 ⭐️⭐️⭐️⭐️ 2. 泛型数据类型 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️ 3. trait:定义共享的行为 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️ 4. 生命周期与引用有效性 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
Rust学习笔记之泛型、trait 与生命周期
|
Rust 编译器 程序员
Rust与生命周期(二)
Rust与生命周期(二)
|
3天前
|
Rust 安全 程序员
|
3天前
|
Rust 安全 程序员
Rust vs Go:解析两者的独特特性和适用场景
在讨论 Rust 与 Go 两种编程语言哪种更优秀时,我们将探讨它们在性能、简易性、安全性、功能、规模和并发处理等方面的比较。同时,我们看看它们有什么共同点和根本的差异。现在就来看看这个友好而公平的对比。
|
8月前
|
Rust Go C++
Rust vs Go:常用语法对比(十三)(1)
Rust vs Go:常用语法对比(十三)(1)
63 0
|
8月前
|
Rust Go C++
Rust vs Go:常用语法对比(十三)(2)
Rust vs Go:常用语法对比(十三)(2)
73 1
|
8月前
|
Rust Go C++
Rust vs Go:常用语法对比(十二)(2)
Rust vs Go:常用语法对比(十二)(2)
59 0