当前位置: 首页 > news >正文

rust中生命周期使用

以这段Go代码为例作为开场

funccreateInt()*int{i:=42// int 类型分配在栈上return&i// 这里由于返回引用类型,分配到堆上}funcmain(){num:=createInt()println(*num)// 程序结束时,num指向的堆内存会被释放}

这是一段Go程序,是健康可运行的,createInt函数返回指针,main函数调用,这里叫做内存逃逸

Go语言中的Gc回收器+逃逸分析,这两个东西组合保障了程序能够正常运行,且不必要担心安全问题

当这段逃逸的堆内存没有地方再引用时,会被回收掉

悬垂引用

一个引用(或指针)仍然指向一块内存地址,但这块内存已经被释放或不再有效。此时再继续操作程序就会异常

下面是一个C语言例子

#include<stdio.h>int*createInt(){inti=42;// int 类型分配在栈上return&i;// 返回 i 的地址}// ⚠️ 函数结束,栈的内存会被回收intmain(){int*num=createInt();// 接收了已释放内存的地址(printf("%d\n",*num);// ⚠️ 未定义行为!可能打印 42,也可能打印垃圾值,或者崩溃return0;}

C语言没Go那一套逃逸分析的自动堆内存分配机制,这里的代码实现就称为悬垂引用,或野指针

如果再C语言中实现该逻辑,需要手动将函数createInt返回内容分配到堆上,为什么要分配到堆上?

因为栈的东西在函数执行完就被回收了,注意哈,C语言没有Gc回收机制,但栈上的内存操作系统是会自动管理的

改良后的完整代码

#include<stdio.h>#include<stdlib.h>// 包含malloc/free的头文件int*createInt(){int*i=(int*)malloc(sizeof(int));// 分配堆内存if(i==NULL){// 检查内存分配是否成功(必做)printf("内存分配失败\n");exit(1);}*i=42;// 给堆内存赋值returni;// 返回堆内存地址(安全)}intmain(){int*num=createInt();printf("%d\n",*num);// 输出 42(安全)free(num);// 释放堆内存(避免内存泄漏)num=NULL;// 置空指针(避免野指针)return0;}

rust中悬垂引用

概念都是一样的,rust中没有C语言的malloc/free,没有Go语言的Gc/逃逸分析

fnmain(){leti=create_int();println!("{}",i);}fncreate_int()->&i32{lets=32;&s}

rust的编译器不仅仅用来转换二进制代码,还提供了代码解决方案,这是完整的报错信息,里面已经提示你正确的代码书写方式

error[E0106]: missing lifetime specifier --> src/main.rs:12:20 | 12 | fn create_int() -> &i32 { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` | 12 | fn create_int() -> &'static i32 { | +++++++ help: instead, you are more likely to want to return an owned value | 12 - fn create_int() -> &i32 { 12 + fn create_int() -> i32 { | For more information about this error, try `rustc --explain E0106`. error: could not compile `naive` (bin "naive") due to 1 previous error

rust中解决悬垂引用的方法为,-> 生命周期

rust 两大数据类型

在搞清楚生命周期之前,要先区分两大类型

  • 所有权类型:你是数据的“主人”,数据存在你的变量里
  • 引用类型:你只是数据的“借阅者”,数据属于别人,你只能看或改,不能销毁它
fnmain(){leti=create_int();letj=create_intptr();// 会报错}// 32 这个值是变量 s 的,该函数返回的是 s,谁接收谁能随便用// 不管你是堆还是栈分配,所有权会自动管理fncreate_int()->i32{lets=32;s}// 32 这个值是变量 s 的,该函数返回的是 s 的引用指针,接收方只能看/改// 但是,函数create_intptr执行结束了,s 释放销毁了,main函数中的 j 将变成悬垂引用fncreate_intptr()->&i32{lets=32;&s}

换句话来说,

在局部块儿(函数、结构体…)返回引用类型,会产生悬空指针;返回所有权类型,可以随便玩

rust 中解决悬空指针的方法为 -> 生命周期

这里整理了一份 所有权类型 和 引用类型 的具体体现

所有权类型

特征:变量直接包含数据(或在堆上拥有数据),当变量离开作用域时,数据会被自动释放(Drop)。
符号:通常没有 & 符号。

类别具体类型示例说明
基本标量类型i32,f64,bool,char这些类型大小固定,直接存储在栈上。赋值时会复制一份新数据。
元组 (Tuple)(i32, bool),(String, i32)如果元组内包含拥有所有权的类型,元组本身也拥有它们。
数组 (Array)[i32; 5],["a", "b"]大小固定,直接存储在栈上(除非很大)。赋值时复制。
结构体 (Struct)struct User { name: String }如果结构体字段是String等拥有所有权的类型,结构体实例就拥有这些数据。
枚举 (Enum)enum Option<T> { Some(T), None }同上,拥有内部数据的所有权。
字符串 (String)String::from("hello")重点:这是堆分配的字符串,变量拥有堆上数据的所有权。
集合 (Collections)Vec<T>,HashMap<K, V>,Box<T>这些都在堆上分配数据,变量拥有堆数据的句柄(指针+容量+长度),负责释放内存。
闭包 (Closure)|x| x + 1(捕获所有权时)闭包可以捕获变量的所有权。

代码示例

leta=10;// a 拥有 10lets=String::from("hi");// s 拥有堆上的 "hi"letv=vec![1,2,3];// v 拥有堆上的数组letmy_struct=User{name:s};// my_struct 现在拥有了 name (s 的所有权转移了)

引用类型

特征:变量不包含数据,只包含数据的地址。它们不负责释放内存。必须依附于某个拥有所有权的变量存在。
符号:必须带有 & (不可变引用) 或 &mut (可变引用)。

类别具体类型示例说明
不可变引用&i32,&String,&str只能读数据,不能改。可以有多个同时存在。
可变引用&mut i32,&mut Vec<i32>可以修改数据。同一时间只能有一个。
切片 (Slices)&[i32],&str重点:切片是对连续内存部分的引用。&str是对字符串数据的引用(通常指向String内部或字面量)。
原始指针*const T,*mut T类似 C 的指针,不安全(unsafe),但也属于引用语义(不拥有所有权)。

代码示例

leta=10;letr=&a;// r 是引用,借用 a,r 的类型是 &i32letst="hello";// 注意:st是引用类型,引用的一个 "hello" 的串,"hello" 这个字符并不属于变量 stlets=String::from("hello");letslice:&str=&s;// slice 是引用,借用 s 的一部分,类型是 &strletmutx=5;letm=&mutx;// m 是可变引用,类型是 &mut i32*m=10;// 通过引用修改数据

rust 生命周期

生命周期是编译器用来确保所有引用都是有效的机制。它们的主要目的是防止悬垂引用,即引用指向了已经被释放的内存

语法规则

生命周期标注以 ’ 开头,通常使用小写字母,如'a,'b,'c,'static等。通常会搭配泛型符号使用。

rust中,泛型不仅仅能用来定义自定义类型,也可用于自定义生命周期类型

static是一个特殊的关键字,下面会单独说

不使用生命周期,会报错

fnmain(){letst=longest("hello","world");println!("{}",st);}fnlongest(x:&str,y:&str)->&str{ifx.len()>y.len(){x}else{y}}

加上生命周期

fnmain(){letst=longest("hello","world");println!("{}",st);}fnlongest<'a>(x:&'astr,y:&'astr)->&'astr{ifx.len()>y.len(){x}else{y}}

这里声明了一个生命周期为'a的泛型,返回的引用也使用了'a生命周期,这样就保证了返回的引用指向的数据和传入的引用指向的数据是同一个生命周期,不会出现悬垂引用

拓展

结构体类型使用

structExcerpt<'a>{part:&'astr,}// impl 方法实现中也需声明生命周期: impl<'a> xxx<'a>impl<'a>Excerpt<'a>{fnreturn_str(&self,announcement:&str)->&str{println!("Announcement: {}",announcement);self.part// 返回的是 self 的一部分,生命周期与 self 绑定}}fnmain(){letst=Excerpt{part:"hello"};println!("{}",st.return_str("world"));}

多个生命周期

fnget_first<'a,'b>(x:&'astr,y:&'bstr)->&'astr{y// 明确返回的是 x 的切片,与 y 的生命周期无关 返回x可编译成功,返回y会编译失败}fnmain(){letst=get_first("hello","world");println!("{}",st);}

'static 生命周期

系统预设的一个特殊的生命周期,表示该引用在整个程序运行期间都有效。(那怕是声明方的程序已经栈回收了)

以字面量字符引用为例let s = "world";,这个类型编辑器显示是&str类型,这里其实给省略了,它应当是一个定义了全局生命周期的&'static str类型。

案例_1

// 报错fnlongest_1()->&str{lets="world";s}// 正常fnlongest_2(x:&str)->&str{lety="world";y}

这是因为rust编译器存在一套生命周期省略规则,有一条规则为如果函数有一个输入引用参数(如 &str),且返回值是引用类型,编译器会自动将返回值的生命周期推断为与这个输入参数的生命周期相同。

longest_1 手动声明返回类型为-> &'static str可以编译通过

这个除外,编译器根据逻辑,不确定使用x还是y的生命周期

fnlongest_6(x:&str,y:&str)->&str{ifx.len()>y.len(){x}else{y}}

案例_2

// 没显示声明,任何生命周期都可以传 'static 'a 'bfnpartial(s:&str){println!("Static string: {}",s);}// 显示声明了 static 周期fnglobal(s:&'staticstr){println!("Static string: {}",s);}

总结

各语言解决悬空指针的方法

  • Go 逃逸分析+Gc垃圾回收
  • C 手动分配堆内存,手动回收
  • rust 生命周期
http://www.jsqmd.com/news/489711/

相关文章:

  • 收藏!程序员小白必看:大模型召回=存储+检索,存储决定检索天花板
  • 大模型持续预训练全解析:如何注入领域知识而不“遗忘”通用能力?
  • 打印机日常维护教程,延长寿命不卡纸,新手一看就会
  • 工业相机图像高速存储(C++版):直接IO存储方法,附Basler相机实战代码!
  • Python面向对象编程(OOP)详解:类、对象、继承、多态、封装
  • 智芯Z20K11x 资源介绍
  • 我知道背单词要坚持,但孩子总是三天打鱼两天晒网。有没有那种自带打卡功能的软件,能提醒他每天必须完成任务?
  • 【回溯算法——N皇后】
  • Thread类中的start()和run()方法有什么区别?
  • 企业做 PCI 认证的综合优势,提升市场竞争力与客户信任度
  • GPU Fence 连续delay引起的anr/swt
  • 重置 Kingbase 数据库的 system 用户密码
  • SAP获取采购预制发票MIR7模拟凭证数据
  • 【Altium Designer 26(AD 26)图文免费安装教程及下载】
  • 高效集成的DCIM管理系统引领数据中心智能化管理革命
  • 论文人自救指南:Paperxie 如何搞定初稿、绘图、排版、AI 率四大难题
  • ART堆内存调整
  • 精通多步推理与动态工具调用:打造高级AI Agent实战指南
  • 3/16 第二节课
  • 告别重复编码!优途 66 Java 代码生成器,10 秒生成 MyBatis-Plus 完整代码套件
  • 2026楚慧杯初赛MISC全解
  • 收藏!90天打造你的AI同事:从0到1落地AI Agent实战清单
  • 科技信息最前沿202603——ADAS
  • Java如何通过JSP实现网页端文件夹的目录结构递归上传并支持军工涉密加密?
  • 分期乐2000元支付宝立减金还能这样回收?快来了解! - 团团收购物卡回收
  • 2026年智能客服系统推荐:紫荆智慧科学技术研究有限公司,AI客服工具/软件全场景赋能 - 品牌推荐官
  • Python 免费开源库精选:那些“不要钱”却“值千金”的神器
  • 2026年成品/一体式/别墅/无边际/恒温/智能泳池推荐:广州思普设备工程全系解决方案 - 品牌推荐官
  • DnsJumper:网页加速神器
  • 如何安装安卓Android Studio