Rust 入门 生命周期

生命周期

  • Rust 中的每一个引用都有其 生命周期(lifetime)
  • 生命周期就是引用保持有效的作用域
  • 大多数情况下: 声明周期是隐式的, 可推断的
  • 当引用的声明周期可能以不同的方式互相关联时, 需要我们手动标注生命周期

声明周期避免了悬垂引用

  • 生命周期的主要作用就是为了避免悬垂引用

  • 示例: 尝试使用离开作用域的值的引用

fn main() { let r; { let x = 5; r = &x; } println!("r: {}", r); } // 外部作用域声明了一个没有初化的变量 r // 而内部作用域声明了一个初值为 5 的变量 x // 在内部作用域中,我们尝试将 r 的值设置为一个 x 的引用 // 接着在内部作用域结束后,尝试打印出 r 的值。 // 这段代码不能编译因为 r 引用的值在尝试使用之前就离开了作用域 // 如下是错误信息: // error[E0597]: `x` does not live long enough // --> src/main.rs:7:5 // | // 6 | r = &x; // | - borrow occurs here // 7 | } // | ^ `x` dropped here while still borrowed // ... // 10 | } // | - borrowed value needs to live until here // 变量 x 没有 “存在的足够久” // 其原因是 x 在到达第 7 行内部作用域结束时就离开了作用域 // 然而 r 在外部作用域仍是有效的 // 作用域越大我们就说它 “存在的越久” // 如果 Rust 允许这段代码工作 // r 将会引用在 x 离开作用域时 被释放的内存 // 这时尝试对 r 做任何操作都不能正常工作

借用检查器

  • Rust 编译器有一个 借用检查器(borrow checker)
  • 它比较作用域来确保所有的借用都是有效的
  • 示例: 尝试使用离开作用域的值的引用 (生命周期标注)
fn main() { let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!("r: {}", r); // | } // ---------+ // 将 r 的生命周期标记为 'a // 将 x 的生命周期标记为 'b // 可以看到 'b 块比 'a 小 // 在编译时, 借用检查器会比较这两个生命周期的大小 // 发现 r 拥有生命周期 'a 但它引用了一个拥有生命周期 'b 的对象 // 此时程序被拒绝编译, 因为生命周期 'b 比生命周期 'a 要小 // 也就是被引用的对象比它的引用者存在的时间更短。
  • 示例: 修改上述代码使其编译通过
fn main() { let x = 5; // ----------+-- 'b // | let r = &x; // --+-- 'a | // | | println!("r: {}", r); // | | // | | } // --+-------+ // 将 r 的生命周期标记为 'a // 将 x 的生命周期标记为 'b // 此时 'b 比 'a 大 // 也就说明了在引用被使用前是有效的

函数中的泛型生命周期

  • 示例: 比较字符串长度
/// 比较字符串的长度, 返回较长的字符串引用 fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } // 如下是错误信息: // error[E0106]: missing lifetime specifier // --> src/main.rs:1:33 // | // 1 | fn longest(x: &str, y: &str) -> &str { // | ^ expected 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 `y` // 编译器此时提示我们返回值需要泛型声明周期参数 // 因为 rust 不知道返回值指向的是参数x还是参数y
  • 示例: 修改上述代码使其编译通过
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // 我们新增了一个泛型参数 'a, 表示这个函数存在一个叫做 'a 的生命周期 // 在引用符号(&)后加上生命周期名称, 就代表生命周期标注 // 这段代码表示的意思是: [x, y, 返回值]都包含相同的声明周期, 但是它们三个的生命周期并不相同

生命周期标注

  • 生命周期标注不会改变引用的生命周期长度
  • 当指定了泛型生命周期参数后, 函数可以接受带有任何生命周期的的引用
  • 生命周期标注: 描述多个引用的生命周期相互的关系, 不会影响其引用的生命周期

生命周期参数名称

  • '开头 (撇号 也叫单引号)
  • 通常使用全小写的命名
  • 非常短, 通常使用一个字母表示

生命周期标注的位置

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

示例

  • &i32 一个引用
  • &'a i32 带有显示标注生命周期的引用
  • &'a mut i32 带有显示标注生命周期的可变引用

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

  • 在函数签名中必须先定义泛型生命周期参数
  • 示例:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { } // 首先在 <> 中声明了一个泛型生命周期参数 // x 和 y 的数据类型为 &'a str // 返回值的类型为 &'a str // 'a 描述了: x 和 y 的的声明周期不能比 'a 短

深入理解生命周期

  • 指定生命周期参数的方式依赖于函数所做的事情
  • 示例:
fn longest<'a>(x: &'a str, y: &str) -> &'a str { x } // 此函数中的y与返回值无关
  • 从函数中返回引用时, 返回类型的生命周期参数必须与参数中的生命周期相匹配

结构体中的生命周期标注

  • 结构体中的字段可以是自持有的类型
  • 结构体中的字段也可以是引用类型
    • 若结构体中的字段是引用类型
    • 需要手动添加声明周期标注
  • 例子:
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 }; }

生命周期的省略

  • 在 Rust 语言的早期版本中, 每个函数都必须显示的标注生命周期
  • 后来 Rust 的开发团队发现有些情况下生命周期是可以被推断的
    于是 Rust 开发团队将一些可以推断声明周期的情况写入借用检查器中
  • 例子: 早期 Rust 的函数生命周期标注(pre-1.0)
fn first_word<'a>(s: &'a str) -> &'a str { }
  • 例子: 现在 Rust 的函数声明周期标注中的省略
fn first_word(s: &str) -> &str { }
  • 需要注意的是: 不是所有的生命周期标注都可以省略

声明周期的省略规则

  • 被编码进 Rust 引用分析的模式被称为 生命周期省略规则(lifetime elision rules)
    • 这些规则不需要开发者来遵守
    • 这是一些特殊情况, 需要由编译器来考虑
    • 如果你的代码符合这些情况, 就不需要显示的标注生命周期
  • 声明周期省略规则不会提供完整的推断
    • 如果应用规则后, 引用的声明周期仍然模糊不清, 就会导致编译错误
    • 解决办法就是手动添加声明周期标注
  • 输入/输出声明周期:
    • 函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes)
    • 返回值的生命周期被称为 输出生命周期(output lifetimes)
  • 声明周期省略规则:
    • 每个引用类型的参数都有自己的声明周期
    • 如果只有一个输入声明周期参数, 那么该生命周期会被赋予所有的输出声明周期参数
    • 如果输入生命周期参数中包含&self或者&mut self, 那么self的声明周期会被赋给所有的输出声明周期参数
  • 编译器使用上述三条规则在没有显式标注声明周期的情况下,来确定引动的声明周期
    • 第一条规则用于输入生命周期
    • 后两条规则用于输出声明周期
    • 如果编译器使用了这三个规则之后仍然无法确定声明周期的引用, 就会编译失败
    • 这三条适用于 fn定义和impl

方法定义中的生命周期标注

  • 在结构体上使用生命周期实现方法, 语法和泛型相同

  • 在哪声明和使用生命周期参数, 依赖于:

    • 生命周期参数是否和字段, 方法的参数或返回值有关
  • 结构体字段的生命周期名:

    • impl后声明
    • 在结构体名称后使用
    • 这些生命周期是结构体类型的一部分
  • 实现块内的方法签名中:

    • 引用必须绑定结构体字段的声明周期, 或者引用独立
    • 生命周期省略规则经常使得方法中的生命周期标注不是必须的
  • 例子:

struct ImportantExcerpt<'a> { part: &'a str, } impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } }

静态声明周期

  • 'static是一个特殊的声明周期
    • 它表示整个程序的运行时间
  • 例如:
    • 所有的字符串字面值就是'static
  • 在为引用指定'static之前一定要三思

例子:

  • 在这个例子中使用了:
    • 泛型参数类型
    • Trait Bound
    • 声明周期
use std::fmt::Display; // 生命周期属于特殊的一种泛型 fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str where T: Display, { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } } fn main() { }
如果对我分享的内容感兴趣的话  记得关注我得动态
求推荐  求收藏  求转发  求关注

__EOF__

本文作者Delayer
本文链接https://www.cnblogs.com/delayer/articles/14986086.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Delayer  阅读(541)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示