惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Register - Security
The Register - Security
V
Visual Studio Blog
aimingoo的专栏
aimingoo的专栏
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
量子位
C
Check Point Blog
博客园 - 【当耐特】
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Engineering at Meta
Engineering at Meta
Recorded Future
Recorded Future
The Last Watchdog
The Last Watchdog
博客园 - Franky
N
Netflix TechBlog - Medium
Webroot Blog
Webroot Blog
A
About on SuperTechFans
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
D
Docker
S
Security Affairs
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
Jina AI
Jina AI
Help Net Security
Help Net Security
L
LangChain Blog
P
Proofpoint News Feed
The Cloudflare Blog
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
Schneier on Security
Schneier on Security
Recent Announcements
Recent Announcements
Attack and Defense Labs
Attack and Defense Labs
云风的 BLOG
云风的 BLOG
V
Vulnerabilities – Threatpost
Microsoft Security Blog
Microsoft Security Blog
H
Heimdal Security Blog
P
Proofpoint News Feed
O
OpenAI News
H
Help Net Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
爱范儿
爱范儿
Security Archives - TechRepublic
Security Archives - TechRepublic

VnYzm的博客

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(六) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) rCore-Tutorial-Book-v3学习笔记(三) rCore-Tutorial-Book-v3学习笔记(二) rCore-Tutorial-Book-v3学习笔记(一) 游戏玩后感:RewritePlus MIT-6.S081-2020实验(xv6-riscv64)十一:net MIT-6.S081-2020实验(xv6-riscv64)十:mmap MIT-6.S081-2020实验(xv6-riscv64)九:fs MIT-6.S081-2020实验(xv6-riscv64)八:lock MIT-6.S081-2020实验(xv6-riscv64)七:thread MIT-6.S081-2020实验(xv6-riscv64)六:cow MIT-6.S081-2020实验(xv6-riscv64)五:lazy MIT-6.S081-2020实验(xv6-riscv64)四:traps MIT-6.S081-2020实验(xv6-riscv64)三:pgtbl MIT-6.S081-2020实验(xv6-riscv64)二:syscall 动漫观后感:吹响吧上低音号 MIT-6.S081-2020实验(xv6-riscv64)一:util 快速生成网络mp4视频缩略图技术 用plantuml画图示例 QQ缩略图和大图不同实现 Python制作字符图片 动漫观后感:命运石之门 使用OpenCV对图片进行特征点检测和匹配
《Rust中常见的有关生命周期的误解》学习笔记
VnYzm · 2026-01-25 · via VnYzm的博客

概述

众所周知,在 Rust 语法里面,生命周期一直是初学者面临的一道雄关。最近部门里 Rust 预研组长分享了《Rust 中常见的有关生命周期的误解》这篇文章,看完刷新了我对包括生命周期在内的一些 Rust 语法的理解。所以这里分享一下学习笔记。

内容

误解 1:T 只包含所有权类型。

这个误解主要想讲的是泛型T既可以匹配所有权类型,也可以匹配引用类型。所以在写一揽子实现的时候不能同时为T&T(或&mut T)实现 trait,不然就重复了。即:

trait Trait {}
impl<T> Trait for T {}
impl<T> Trait for &T {} // 编译错误
impl<T> Trait for &mut T {} // 编译错误

但这里我更关注的是“Rust 能够给引用类型实现 trait”这个概念。比如我可以这样写:

trait Trait {
    fn f(&self);
}
impl Trait for &i32 {
    fn f(&self) {
        println!("{}", self);
    }
}
let a: i32;
(&a).f();

我一开始一直想不通,为什么能给引用类型实现方法。后来才发现我被过去学习面向对象语言的经验给误导了,总是把实现 trait 当作是接口继承一类的东西。实际上,面向对象里,讲究的是“A 是 B 的一种”这样的关系。比如,“鸭子”类继承“鸟”类,因为鸭子就是鸟的一种。但 Rust 里,实现 trait 更像是一种“A 能做 B 定义的行为”的关系。比如,“有翅膀”是一个 trait,其中包含了“扇翅膀”、“收起翅膀”等行为,那么,“鸭子”结构体实现了这个 trait,它就有了对应的行为,“蝙蝠”结构体也实现了这个 trait,也有对应的行为,但它不是鸟。

反映到上面的代码里,我是让 &i32 能做 f() 这个行为,而不是让 &i32 作为 Trait 的一种。这样逻辑就很顺了。

误解 2、3:如果 T: ’static 那么 T 直到程序结束为止都一定是有效的;&’a T 和 T: ’a 是一回事。

误解 2 和 3 其实是一样的,说的是给泛型参数T而不是&T/&mut T指定生命周期的语义。前面也说了,T 能够匹配所有权类型,也能匹配引用类型,而所有权类型的生命周期并没有什么意义。所以,如果 T 匹配的是所有权类型,那么这个'static或者'a就相当于没用,只有当 T 匹配的是引用类型时,才会要求这个引用类型的参数或者属性需要满足'static或者'a生命周期约束。

误解 4:我的代码里不含泛型也不含生命周期注解。

没啥好说的,有时生命周期注解可以省略,编译器有一套规则决定是否可以省略。

误解 5:如果编译通过了,那么我标注的生命周期就是正确的。

感觉出现这个问题的场景比较极端,只有当结构体里包含引用属性,并且给这个结构体实现方法的时候,参数只有一个&self,且返回这个结构体里的引用属性时才会遇到推断错误的情况。我写了个更简单的例子触发这种场景:

struct s<'a> {
    r: &'a i32 // 结构体里包含引用属性    
}

impl s<'_> {
    fn f(&self) -> &i32 { // 方法里只有一个 &self 参数
        self.r // 返回值是这个引用属性
    }
}

fn main() {
    let a: i32 = 0;
    let r = {
        let ss = s { r: &a };
        ss.f() // `ss` does not live long enough
    };
    println!("{}", r);
}

返回值本应该关联s.r的生命周期,结果编译器让返回值关联到&s自己的生命周期了,进而导致了此错误,改成这样即可:

struct s<'a> {
    r: &'a i32    
}

impl<'a, 'b> s<'a> {
    fn f(&'b self) -> &'a i32 { // 'b 可以不标,让编译器自动推断
        self.r
    }
}

fn main() {
    let a: i32 = 0;
    let r = {
        let ss = s { r: &a };
        ss.f()
    };
    println!("{}", r);
}

拓展:如果我把方法f换成一个独立的函数f,如下所示:

fn f(ss: &s) -> &i32 {
    ss.r
}

这个时候,编译器就不会自动推断,而是要求我显式标注生命周期,并给出推荐:

help: consider introducing a named lifetime parameter
  |
5 | fn f<'a>(ss: &'a s<'a>) -> &'a i32 {
  |     ++++      ++  ++++      ++

这个推荐是有问题的(提前剧透误解 7 了😂)。可以看到这里一股脑地将ssss.r和返回值全部标注为同一生命周期,因此这样写会报和第一个反例一样的错误。解决方法就是按照真实情况,返回值和ss.r标同个生命周期,ss自己标独立生命周期,就对了:

fn f<'a, 'b>(ss: &'a s<'b>) -> &'b i32 {
    ss.r
}

当然,这些问题都是编译期出现的,不懂的话最多编译不过,不会把问题留到运行时。

另外,我也让豆包看了下,我写的第一个反例豆包直接就改对了。而原文中的反例,豆包一开始给了些其他方案,我让它只考虑改生命周期标注,它也能准确地指出问题。所以即使不懂,靠 AI 帮忙应该是一个可行的解决方案。

误解 6:已装箱的 trait 对象不含生命周期注解

要理解这个误解,首先要理解dyn Trait + 'static是什么意思。dyn Trait这玩意其实和泛型类型T有点像的,都是既可以指代所有权类型,也可以指代引用类型。所以,dyn Trait + 'static的含义就和T: 'static类似,即要求dyn Trait这个类型的实例,如果是所有权类型,那就没限制;如果是引用类型,那么生命周期就要满足'static限制。像下面这个例子,就会报错:

trait Trait {}

impl Trait for &i32 {}

fn main() {
    let v: i32 = 0;
    let b: Box<dyn Trait + 'static> = Box::new(&v); // `v` does not live long enough
}

而这个误解说的是,直接声明Box<dyn Trait>,编译器就会自动推断为Box<dyn Trait + 'static>。与之对应的,如果声明&dyn Trait,那么编译器就会自动推断dyn Trait的声明周期和这个引用本身的声明周期一致。即

// 展开前
type T3<'a> = &'a dyn Trait;
// 展开后,&'a T 要求 T: 'a, 所以推导为 'a
type T4<'a> = &'a (dyn Trait + 'a);

其实这也很合理,因为编译器推断生命周期,总得有个依据。而Box是拥有所有权的指针,它不是借用,因此是没有生命周期的,所以你让编译器推Box<dyn Trait>的生命周期,它也只能推成默认的'static。如果你指定了 Trait 内部的生命周期,编译器有依据了,就不会推成'static了:

// 展开前
type T7<'a> = Box<dyn GenericTrait<'a>>;
// 展开后
type T8<'a> = Box<dyn GenericTrait<'a> + 'a>;

误解 7:编译器的报错信息会告诉我怎样修复我的程序

这个前面也说过了,编译器一般会推荐一股脑地将所有参数、参数内部、返回值全部标注为同一生命周期,这种推荐很多时候不满足用户需求。此外如误解 6 所示,编译器有时会自动推断生命周期,那么它给出的推荐也往往是根据前面自动推断的生命周期来的,如果自动推断的生命周期本来就不满足用户需求,那给出的推荐当然就更不满足用户需求了。

误解 8:生命周期可以在运行时动态变长或变短

生命周期推断比较粗糙,在编译期直接在所有可能接受的引用中选一个最早被释放的。并且这种推断是在各类编译优化之前。

误解 9:将独占引用降级为共享引用是 safe 的

b = &*(&mut a)会导致b存活的时候&mut a也一直存活,在此期间不能再获取&a。同时如果原来的&mut a绑定了变量(如let c = &mut a; let b = &*c;),那b的存活期间c也不能用来给a赋值了(不能执行*c = XXX)。所以原文说这是一个“反模式”,意思就是这个用法没有禁止但百害无一利。

另外注意原文中的误解 5 也涉及了这个用法,而且更隐蔽。原文中的next方法:

fn next(&mut self) -> Option<&u8> {
    if self.remainder.is_empty() {
        None
    } else {
        let byte = &self.remainder[0];
        self.remainder = &self.remainder[1..];
        Some(byte)
    }
}

前面说了编译器会将返回的&u8推断为和&mut self相同的生命周期,那么,当执行let byte = &self.remainder[0];时,根据DeRef隐式转换,实际上执行的是let byte = &(*self.remainder)[0];,又因为生命周期推断,所以在byte(返回的&u8)存活期间,不是self.remainer一直存活,而是&mut self一直存活,而第二次调用next方法时会又获取一次&mut self,导致独占引用重复获取错误。

误解 10:对闭包的生命周期省略规则和函数一样

意思就是匿名函数的自动生命周期推断规则和声明方式都和普通函数不一样。总结下来原文给出的合法方案就两种:

// 可以不用堆分配而直接创建一个 'static 引用
let identity: &dyn Fn(&i32) -> &i32 = &|x: &i32| x;

    // 上一行去掉语法糖 :)
let identity: &'static (dyn for<'a> Fn(&'a i32) -> &'a i32 + 'static) = &|x: &i32| -> &i32 { x };

挺麻烦的,可能 Rust 设计初衷就希望我们在用匿名函数的时候引用尽量通过闭包而不是参数来获得。

误解 11:’static 引用总能被强制转换为 ’a 引用

总结起来,就是'static引用能当'a引用使用,但是返回'static引用的函数不能当返回'a引用的函数使用。

总结

在以上所有误解中,有好几个都是因为 Rust 编译器的生命周期自动推断机制导致的,这其实是一个两难问题:自动推断少,虽然会减少排查编译错误的困难,但会提高编程复杂度,同时也让 Rust 本就不低的学习门槛被进一步抬高;但自动推断多,有些细节被隐藏,导致初学者在学习的时候很难学透,导致一些比较刁钻的编译错误(所幸现在有 AI 能在一定程度上解决这类问题)。要是有一个能自动显示出所有被省略的生命周期的工具就好了,看了下 rust-analyzer 插件没这功能,Cargo/rustc 好像也没这种选项,问豆包也答错了,不知道有没有人能开发一个。

此外,感觉 Rust 很需要一本“Effect Rust”这样的书,通过各种“条款”让初学者能够深入理解 Rust 里的一些细节。原文章虽然能起到一部分的作用,但它的门槛还是比较高,比基础教程往上一大截,还有很多例子太复杂了,重点不够突出,因此对刚学完基础教程的我来说,学习难度也比较大,需要做很多实验才能理解。

不过,不管怎么说,这些误解所产生的后果也不过是编译错误,不会像 C/C++ 一样写个 UB 导致各种花样百出的 BUG 来,这点还是比较令人安心的。