rust lifetime

Rust 生命周期

首先每个引用都有生命周期,也就是引用保持有效的作用域

一个引用的作用域从声明的地方开始一直持续到最后一次使用为止

let a=String::from("a");
let b=&a;//b的诞生 ,后续没有在使用b,所以b死亡

在借用者生命期间,所有者必须是活的,不然就会产生悬垂引用,幸运的是我们不用关注它,交给编译器来提示,编译器通过生命周期来检查
大部分时候生命周期是隐含并可以推断的,但有些情况就无法推断了,需要程序员自己指出

fn longest(x: &String, y: &String) -> &String {//这个函数会报错,具体原因我们后面会讲到
	if x.len() > y.len() {//可以理解成随机返回 x 或 y,因为在运行时2种情况都会出现
		x
	} else { 
		y 
	}
}
fn main() {
	let a=String::from("a");
	let c;
	{
		let b=String::from("b");
		c = longest(&a,&b);//我们并不知道它会返回a还是b,这导致生命周期的不确定性,那么此时c就是不安全的,你不敢在大括号外使用c
	}
}

下面是修改后

fn longest<'a>(x: &'a String, y: &'a String) -> &'a String {//统一生命周期,按照最小生命周期来分析
	if x.len() > y.len() {
		x
	} else {
		y
	}
}
fn main() {
	let a=String::from("a");
	let c;
	{
		let b=String::from("b");
		c = longest(&a,&b);
		println!("{}",c);//安全
	}//b在这里就死了
	// println!("{}",c);//这行会报错,因为最小生命周期是b
}

注意:生命周期声明类似于变量类型声明,不会改变对象的真正生命周期。当你生命的生命周期和实际不符合的时候,编译器会报错。

函数或方法的参数的生命周期被称为 输入生命周期,而返回值的生命周期被称为 输出生命周期
隐式生命周期,官方介绍了3条规则

每一个是引用的参数都有它自己的生命周期参数
如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self, 那么所有输出生命周期参数被赋予 self 的生命周期
你应该意识到了,其实都是隐式存在生命周期的,上面只不过是编辑器无法分析,要求我们显式声明

fn f(x:&i32){}
fn f<'a>(&'a i32){}

fn f2(x:&String)->&String{}
fn f2<'a>(x:&'a String)->&'a String{}
//为什么返回值一定是'a ,因为如果是函数内部的所有者,那么返回出去的借用者就是 悬垂引用,因为在退出函数时 那个所有者就死了
//所以当 参数们只有一个生命周期时,那么返回值也一定是那个生命周期

fn f3(x:&i32,y:&i32,...){}
fn f3<'a,'b,...>(x:&'a i32,y:&'b i32,...){}
//再多参数也是这样,没必要显式声明,咱就懒点

fn f4<'a,'b>(x:&'a String,y:&'b String) -> &'a String {
	// &String::from("") //报错,生命周期不是'a
	// &y	//报错,原因同上
	&x
}

关于第三条,特别说明一下

impl ABC {//如果ABC 是一个加工用的对象,那么就不应该返回&self的生命周期
	fn f1(&self,a:&String)->&String{//返回的生命周期是&self的
		a //报错因为生命周期不同
	}
	fn f2<'a>(&self,a:&'a String)->&'a String{//但是可以这样
		a
	}
}

生命周期语法是用于将函数的多个参数与其返回值的生命周期进行关联的。一旦他们形成了某种关联,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为

结构体定义
struct ABC<'a>{
	A:&'a i32
}
impl<'a> ABC<'a> {
	fn f1(x:&'a i32){}
	fn f2(&self,x:&'a i32){}
}
struct ABCD<'a>{//一样
	A:&'a i32,
	B:&'a i32
}
struct ABCDE<'a,'b>{//不一样
	A:&'a i32,
	B:&'b i32
}

静态生命周期
'static 其生命周期能够存活于整个程序期间

不过将引用指定为 'static 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效。你也许要考虑是否希望它存在得这么久,即使这是可能的。大部分情况,代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,请解决这些问题而不是指定一个 'static 的生命周期

白话说就是 它是否真的可以存活整个程序期间,不是靠’static,而是这个引用是否真实可以存活,前面提到:生命周期声明类似于变量类型声明,不会改变对象的真正生命周期,所以’'static’只是告诉编译器而已

所有的字符串字面值(&str)都拥有 'static 生命周期

生命周期声明是入参和返回值或者结构体成员之间的一种生命周期约定和限制

不同于Rust中的泛型参数,程序员是可以手动指定的。Rust的生命周期是不能手动指定的,需要编译器根据传入的参数进行推断。当编译器在某条语句上不能根据参数进行推断时,他会继续往下执行并推断生命周期参数。编译器会持续根据语句上下文推断出生命周期参数,并选择最小的那个。

struct Context<'a> {
    vars: Vec<&'a str>,
}

fn main() {
    let mut v = Context { vars: vec![] };
    v.vars.push("hello");

    println!("{:?}", v.vars);
}

对于上述程序片段来说,当运行let mut v = Context { vars: vec![] }的时候,此时’a并不能推断出来,于是编译器继续查看下一条语句,当看到v.vars.push(“hello”)时,编译器推断出’a为’static。

同理,当运行下列片段时:

struct Context<'a> {
    vars: Vec<&'a str>,
}

fn main() {
    let mut v = Context { vars: vec![] };
    v.vars.push("hello");               // 'a

    {
        let s = String::from("dd");     // 'b
        v.vars.push(&s);
    }
    println!("{:?}", v.vars);
}

编译器首先会在v.vars.push(“hello”)推断出’a为’static,然后当运行到里面的大括号的时候,发现v.vars.push(&s),而s的生命周期为’b,此时编译器发现两次推断不一样,于是他会选择生命周期较小的那个’b。而此时v的生命周期大于’b,编译器会报错,提示borrowed value does not live long enough。

对于大型程序来说,生命周期推断往往比较复杂。当编译器报错的时候,我们要扮演一次编译器的角色来弄清楚错误究竟是什么,此时这条原则就很有用。

生命周期声明是入参和返回值或者结构体成员之间的一种生命周期约定和限制
这一条其实之前谈过了,这里拿出来是用来强调这一点。在我看来,很多生命周期错误都是没有好好理解生命周期表现的意义而出现的。

struct Context<'a> {
    name: &'a str,
    vars: Vec<&'a str>,
}

例如对于上面的结构体,我们可以看到,编程者的意思是希望name和vars是通过同一生命周期作用域引进来的。但是这确定是你想要的吗?

假设我们现在在编写一个自定义语言运行时,Context是我们的运行上下文。在不同的Context中,vars是上下文的变量名字,而name是我们为不同上下文命名的名字。当我们创建一个新的上下文时,我们会给每个Context创建一个新的临时名字。很明显,vars应该和自定义语言字符串拥有同一生命周期,因为vars应该引用那些语言字符串,而name则是我们人为加上的自定义字符串。如果name是一个临时的名字,则我们就会把vars标记为和name一样的临时生命周期。此时我们的Context将会毫无用处!我们的Context将只能在这个短暂的临时生命周期中运行!

在这种情况下,我们需要把上面的写成如下所示:

struct Context<'a, 'b> {
    name: &'a str,
    vars: Vec<&'b str>,
}

此时,name和vars将有不同的生命周期参数,编译器会分别推断出他们的生命周期。于是问题就解决了。

警惕Self的隐含生命周期参数
对于impl中的方法来说,self有一个隐含的生命周期参数。

struct A<'a> {
    name: &'a str,
}

impl<'a> A<'a> {
    fn get(&self) -> &str {
        self.name
    }
}

fn main() {
    let s = String::from("hello");
    let s_ref;

    {
        let a = A { name: &s };
        s_ref = a.get();
    }
    println!("{:?}", s_ref);
}

对于A的get方法来说,&self有一个隐含的生命周期参数,这个生命周期就是实例化A所在的区域。如果返回的&str不写生命周期参数,根据生命周期省略原则,返回的参数将会和&self一样的生命周期。

在上述示例中,返回的&str的生命周期明显大于self的生命周期。但是在这里返回的str将会限制在A的实例所在的生命周期内。当A的实例a脱离内部作用区域时,s_ref生命周期就结束了,也不能被引用了。

struct A<'a> {
    name: &'a str,
}

impl<'a> A<'a> {
    fn get(&self) -> &'a str {
        self.name
    }
}

fn main() {
    let s = String::from("hello");
    let s_ref;

    {
        let a = A { name: &s };
        s_ref = a.get();
    }
    println!("{:?}", s_ref);
}

正确的是我们显式声明我们的返回值的生命周期为’a,于是一切都正常了,此时s_ref的生命周期就扩充到了外部作用域,println就可以正常打印了。

有的时候,我们希望内部的引用变量和self具有相同的生命周期。此时我们需要显示声明self的生命周期参数。就像下面一样。

struct A<'a> {
    name: &'a str,
}

impl<'a> A<'a> {
    fn set(&'a self, name: &'a str) -> A<'a> {
        A { name }
    }
}

在&self中间加上’a生命周期参数,能够显示声明self和其他参数的生命周期的关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值