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

玩转Rust高级应用 如何编译器对于省略掉的生命周期,不使用“自动推理”策略呢?

所以它比其他任何生命周期都长。这意味着,任意一个生命周期 'a 都满足’static:'a。

如果我们把变量t 的真实生命周期记为’t, 那么这个生命周期 t实际上是变量t从“出生”到“死亡”的区间,即从第11行到第14行。在函数被调用的时 候,它传入的实际参数是&t,它是指向t 的引用。那么可以说,在调用的时候,这个泛型 参数 a 被实例化为了t。根据函数签名,基于返回类型的生命周期与参数是一致的,可以推理出test 函数的返回类型是&'ti32。如果我们把x 的生命周期记为 x, 那么 x 代表的就是从第12行到第14行。

这条let x =text(&t);语句实际上是把&'t i32类型 的变量赋值给&'x i32类型的变量。这个赋值是否合理呢?它应该是合理的。因为这两个 生命周期的关系是’t:'x 。test返回的那个指针在 t 这个生命周期范围内都是合法的,在一个被 t包围的更小范围的生命周期内,它当然也是合法的。所以,上面这个例子可以 编译通过。

接下来,我们把上面这个例子稍作修改,让test 函数有两个生命周期参数,其中一个给 函数参数使用,另外一个给返回值使用:

fn test<'a,'b>(arg:&'a T)->&'b i32
{  &arg.member
}

编译时果然出了问题,在&arg.member 这一行,报了生命周期错误。这是为什么呢? 因为这一行代码是把&a i32类型赋值给&b i32类型。a 和 b 有什么关系?答案 是什么关系都没有。所以编译器觉得这个赋值是错误的。怎么修复呢?指定 a:b 就可以 了 。a比 b“活”得长,自然,&'a i32类型赋值给&'b i32类型是没问题的。验证如下:

fn test<'a,'b>(arg:&'a T)->&'b i32
where 'a:'b
{&arg.member
}

经过这样的改写后,我们可以认为,在test函数被调用的时候,生命周期参数 'a和 'b 被分别实例化为了’t和 'x。它们刚好满足了where条件中的 't:x 约束。而&arg. member 这条表达式的类型是&¹t i32,返回值要求的是& ¹x i32类型,可见这也是合法 的。所以test函数的生命周期检查可以通过。

上述示例是读者比较难理解的地方。以下两种写法都是可行的:

fn test<'a>(arg:&'a T)->&'a i32
fn test<'a,'b>(arg:&'a T)->&'b i32 where 'a:'b

这里的关键是,Rust 的引用类型是支持“协变”的。在编译器眼里,生命周期就是一个 区间,生命周期参数就是一个普通的泛型参数,它可以被特化为某个具体的生命周期。

我们再看一个例子。它有两个引用参数,共享同一个生命周期标记:


fn select<'a>(arg1:&'a i32,arg2:&'a i32)->&'a i32 {if *arg1 >*arg2 {arg1}else{arg2}
}
fn main(){let x =1;let y =2;let selected =select(&x,&y);println!("{}",selected);
}

上述示例中,select 这个函数引入了一个生命周期标记,两个参数以及返回值都是用 的这个生命周期标记。同时我们注意到,在调用的时候,传递的实参其实是具备不同的生命 周期的。x 的生命周期明显大于y 的生命周期,&x 可存活的范围要大于&y 可存活的范围, 我们把它们的实际生命周期分别记录为x 和y 。select函数的形式参数要求的是同样的 生命周期,而实际参数是两个不同生命周期的引用,这个类型之所以可以匹配成功,就是因 为生命周期的协变特性。编译器可以把&x 和 &y 的生命周期都缩小到某个生命周期 a 以 内,且满足x :a,y:a。返回的selected 变量具备 a 生命周期,也并没有超过 x 和 y 的范围。所以,最终的生命周期检查可以通过。


类型的生命周期标记

如果自定义类型中有成员包含生命周期参数,那么这个自定义类型也必须有生命周期参 数。示例如下:

struct      Test<'a>{
member:&'a    str }

在使用impl 的时候,也需要先声明再使用:

impl<'t>Test<'t>{fn test<'a>(&self,s:&'a str){}
}

impl后面的那个’七是用于声明生命周期参数的,后面的 Test<'t>是在类型中使用这 个参数。如果有必要的话,方法中还能继续引入新的泛型参数。

如果在泛型约束中有where T: 'a 之类的条件,其意思是,类型T 的所有生命周期 参数必须大于等于 'a。要特别说明的是,若是有where T:'static的约束,意思则是, 类型T 里面不包含任何指向短生命周期的借用指针,意思是要么完全不包含任何借用,要么 可以有指向’static 的借用指针。


省略生命周期标记

在某些情况下,Rust 允许我们在写函数的时候省略掉显式生命周期标记。在这种时候, 编译器会通过一定的固定规则为参数和返回值指定合适的生命周期,从而省略一些显而易见 的生命周期标记。比如我们可以写这样的代码:

fn get_str(s:&String)->&str{s.as_ref()
}

实际上,它等同于下面这样的代码,只是把显式的生命周期标记省略掉了而已:

fn get_str<'a>(S:&'a String)->&'a str{s.as_ref()
}

若把以上代码稍微修改一下,返回的指针将并不指向参数传入的数据,而是指向一个静 态常量,代码如下:

fn get_str(s:&String)->&str{println!("call fn{}",s);"hello world"
}

这时,我们期望返回的指针实际上是& 'static str 类型。测试代码如下:

fn main(){
let c      =String::from("haha");
let x:&'static       str      =get_str(&c);
println!("{}",x);
}

可以看到,在get_str 函数中,返回的是一个指向静态字符串的指针。在主函数的调 用方,我们希望变量x 指向一个“静态变量”。可是这一次,我们发现了编译错误:

error: c does not live long enough

按照分析,变量x 理应指向一个 static 生命周期的变量,根本不是指向C 变量,它 的存活时间足够长,为什么编译器没发现这一点呢?这是因为,编译器对于省略掉的生命周 期,不是用的“自动推理”策略,而是用的几个非常简单的“固定规则”策略。

这跟类型自 动推导不一样,当我们省略变量的类型时,编译器会试图通过变量的使用方式推导出变量的 类型,这个过程叫 “type inference”。而对于省略掉的生命周期参数,编译器的处理方式就 简单粗暴得多,它完全不管函数内部的实现,并不尝试找到最合适的推理方案,只是应用几 个固定的规则而已,这些规则被称为“lifetime elision rules”。

以下就是省略的生命周期被自 动补全的规则:

  • 每个带生命周期参数的输入参数,每个对应不同的生命周期参数;
  • 如果只有一个输入参数带生命周期参数,那么返回值的生命周期被指定为这个参数;
  • 如果有多个输入参数带生命周期参数,但其中有&self、&mut self,那么返回值 的生命周期被指定为这个参数;
  • 以上都不满足,就不能自动补全返回值的生命周期参数。

这时再回头去看前面的例子,可以知道,对于这个函数:

fn get_str(s:&String)->&str{println!("call fn{}",s);"hello world"
}

编译器会自动补全生命周期参数:

fn get_str<'a>(s:&'aprintln!("call"hello world" String)->&'a fn{}",s);	str{
}

所以,当我们调用

let x:&static str =get_str(&c);

这句代码的时候,就发生了编译错误。了解了这些,修复方案也就很简单了。在这种情 况下,我们不能省略生命周期参数,让编译器给我们自动补全,自己手写就对了:

fn get_str<'a>(s:&a String)->&static str{println!("call "hello world" fn{}",s);
}

或者只手写返回值的生命周期参数,输入参数靠编译器自动补全:

fn get_str(s:&String)->&static str{}

最后, 一句话总结,elision !=inference,省略生命周期参数和类型自动推导的原理是完 全不同的。

http://www.dtcms.com/a/576994.html

相关文章:

  • Python全栈项目:基于Django的电子商务平台开发
  • 网站建设怎么开票网站设计网页设计公司
  • Python实现GPT自动问答与保存
  • 深度强化学习,用神经网络代替 Q-table
  • seo网站建设技巧电线电缆技术支持中山网站建设
  • supabase外键查询语句
  • 【linux端cursor CLI常用命令】
  • 表的增删改查
  • Git 工作区、暂存区和版本库
  • MIT-矩阵链相乘
  • Go语言实战:入门篇-5:函数、服务接口和Swagger UI
  • 国产化Excel处理控件Spire.XLS教程:使用Java将CSV转换为PDF(含格式设置)
  • 【Hot100|3 LeetCode 128. 最长连续序列】
  • 一键搭建 Coze 智能体对话页面:支持流式输出 + 图片直显,开发效率拉满!
  • 十大免费ae模板网站短视频素材下载网站
  • 那里做直播网站网页打不开是什么问题
  • 论文分享 | AirRoom:物体是关键!革新室内房间重识别的新范式
  • 游戏 IPA 如何防修改,面向开发者的多工具实战(IPA 加固/无源码混淆/Ipa Guard CLI)
  • 从0到1做一个“字母拼词”Unity小游戏(含源码/GIF)- 项目的创建及准备
  • 在扣子上搭建测试用例自动编写智能体
  • 2023年第二十届五一数学建模竞赛-A题 无人机定点投放问题-基于抛体运动的无人机定点投放问题研究
  • 影刀RPA一键生成销售日报!AI智能分析,效率提升1000%[特殊字符]
  • Rust开发实战之密码学基础——哈希计算与对称加密实战
  • 技术解析:清洗无人机在高空清洁中的应用与优势
  • Linux LVM NAT 模式部署实践
  • 使用 DVC(Data Version Control)进行数据版本管理
  • 网站建设选择哪种开发语言最好从哪里下载wordpress
  • 微服务之网关(Spring Cloud Gateway)
  • ES脚本语言Painless介绍
  • 基于MATLAB的雨流计数法疲劳计算GUI可视化系统