网站流程图,软文免费发布平台,全国企业信用信息,学做网站的步骤喜欢的话别忘了点赞、收藏加关注哦#xff08;加关注即可阅读全文#xff09;#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵#xff01;(#xff65;ω#xff65;)
10.7.1. 深入理解生命周期
1.指定生命周期参数的方式依赖于函数所做的事情
以上一篇文章的…喜欢的话别忘了点赞、收藏加关注哦加关注即可阅读全文对接下来的教程有兴趣的可以关注专栏。谢谢喵(ω)
10.7.1. 深入理解生命周期
1.指定生命周期参数的方式依赖于函数所做的事情
以上一篇文章的代码为例子
fn longesta(x: a str, y: a str) - a str { if x.len() y.len() { x } else { y }
}这里的函数签名之所以这么写是因为不确定返回值到底是x还是y。如果我修改代码比如把返回值固定为x那么就没必要给y写一个显式生命周期了
fn longesta(x: a str, y: str) - a str { x
}所以这个代码的函数签名就没有给y限制生命周期。
2.当函数返回引用时返回类型的生命周期参数需要与其中一个生命周期匹配
如果返回的引用没有指向任何参数返回的内容就会变成悬空引用因为在函数内创建的值在函数结束的时候就离开了作用域返回的引用指向的就是被释放的内存。
看个例子
fn longesta(x: a str, y: str) - a str { let result String::from(Something);result.as_str()
}在这个函数里创建了一个String类型的result然后调用result上的as_str方法返回字符串切片(str)其实就是一个引用然后就报错了
error[E0515]: cannot return value referencing local variable result-- src/main.rs:13:5|
13 | result.as_str()| ------^^^^^^^^^| || returns a value referencing data owned by the current function| result is borrowed here报错信息是无法返回引用本地变量result的值因为这块返回的值是函数内部持有的数据其实就是刚才说的原因当内部数据离开作用域后就会被清除。
那如果我就想要把函数内部创建值作为返回值改怎么写呢那就不返回引用直接返回这个值
fn longest(x: str, y: str) - String { let result String::from(Something);result
}这样就相当于把函数的所有权移交给调用者了要清理这块内存就由调用者来清理。这样写也不需要显式声明声明周期了因为返回值与参数根本没关系而且只有引用才有生命周期问题。
通过这个例子可以看到生命周期的语法在根本上就是用来关联函数的不同参数以及返回值之间的生命周期的。 一旦它们取得了某种联系Rust就获得了足够的信息来支持保证内存安全的操作并且组织可能会导致悬垂指针或是其他破坏内存安全的操作。
10.7.2. 结构体中的生命周期标注
在前面的文章里我们在结构体中只定义过自持有的类型比如i32、String。而实际上结构体的字段也可以是引用类型如果是引用的话就需要在每个引用上添加生命周期标注。
看个例子
struct ImportantExcerpta {part: a str,
}fn main() {let novel String::from(Call me Ishmael. Some years ago...);let first_sentence novel.split(.).next().unwrap();let i ImportantExcerpt {part: first_sentence,};
}ImportantExcerpt下只有一个字段part其类型是字符串切片也就是一个引用类型。因为它是引用类型所以就需要标注生命周期。
生命周期标注的方法和泛型一样就在结构体后面加在里面写生命周期泛型类型参数即可这里写的是a。part这个引用必须要比这个结构体实例的存活时间要长。因为只要实例存在就会一直有part这个引用如果part先没有那么实例肯定会出错。
看main函数里面先创建了一个String类型的novel然后通过split和next方法来提取出这个字符串里的第一个句子(unwrap是用来解包Option类型的在 9.2. Result枚举与可恢复的错误 Pt.1 中有过介绍)。这个句子的类型是str也就是一个引用。然后创建了ImportantExcerpt这一结构体的实例i把这个引用作为part字段的值。
这样写是没有错误的因为first_sentence这个引用的作用域是从第7行到第11行而i的作用域是从第8行到第11行所以说part这个字段的存活时间比实例长并且能完全覆盖i的生命周期。
10.7.3. 生命周期的省略
每个引用都有生命周期并且需要为使用生命周期的函数或结构体指定生命周期参数。
那为什么这段代码(来自 4.5. 切片Slice)没有生命周期也能通过编译呢
fn main() {let s String::from(Hello world);let word first_word(s);println!({}, word);
}
fn first_word(s:str) - str {let bytes s.as_bytes();for (i, item) in bytes.iter().enumerate() {if item b {return s[..i];} }s[..]
}这个函数在没有生命周期注释的情况下编译的原因是有历史的在 Rust 的早期版本1.0 之前中这个代码不会通过编译因为当时要求每个引用都需要一个显式的生命周期。函数签名就得这样写
fn first_worda(s: a str) - a str {后来Rust团队发现在某些特定情况下Rust程序员总会一遍又一遍地写同样的生命周期标注而且这些场景是可预测的这些场景有一些明确的模式于是Rust团队就将这些模式直接写入了编译器代码使得借用检查器在这些情况下可以自动地推导生命周期而无需程序员显式标注。
了解这段历史的意义在于未来可能会有更多确定性模式可能会出现并被添加到编译器中。将来可能需要更少的生命周期注释谢天谢地。
刚才说的这些在Rust引用分析中所编入的模式称为生命周期省略规则。这些规则无需程序员来遵守它们是一些特殊情况由编译器来考虑。如果你的代码符合这些情况那就无需显式标注生命周期。
但是生命周期省略规则不会提供完整的推断如果在应用了这个规则以后引用的生命周期仍然模糊不清那么仍然会引发编译错误。解决办法就是手动添加生命周期表明引用间的相互关系。
10.7.4. 输入、输出生命周期
如果生命周期出现在函数/方法的参数中那么这类生命周期就叫做输入生命周期。
如果它出现在函数/方法的返回值中那么就叫做输出生命周期。
10.7.5. 生命周期省略的三个规则
编译器使用3个规则在没有显式标注生命周期的情况下来确定引用的生命周期
规则1用于输入生命周期规则2、3用于输出生周期如果编译器在应用完3个规则后仍然有无法确定生命周期的引用就会报错这3个规则不但适用于函数或是方法的定义也适用于impl块
规则1 每个引用类型的参数都有自己的生命周期。 单参数的函数就有1个生命周期双参数的函数就有两个以此类推。
规则2 如果只有1个输入生命周期参数那么该生命周期被赋给所有的输出生命周期参数。 就是单参数的生命周期只有1个这个生命周期就是这个函数所有可能返回值的生命周期。
规则3 如果有多个输入生命周期参数但其中一个是self或mut self也就是说是这个函数是方法那么self的生命周期会被赋给所有输出的生命周期参数。
1. 成功例
规则讲完看看例子
fn first_word(s:str) - str {//...
}把自己带入一下编译器想想对于这个函数签名如何根据3条规则来找到省略的生命周期。
首先应用第一条规则——每个引用类型的参数都有自己的生命周期。这里只有一个参数所以就只有一个生命周期。所以到这一步编译器推断出了:
fn first_worda(s:a str) - str {//...
}由于只有一个输入生命周期所以第2条规则在这里也适用——如果只有1个输入生命周期参数那么该生命周期被赋给所有的输出生命周期参数。 所以输入生命周期就被赋予给了输出生命周期。到这一步编译器推断出了:
fn first_worda(s:a str) - a str {//...
}由于只有一个输入生命周期且这个函数不是方法所以第3条不适用。
而现在函数中所有的引用都有了生命周期因此编译器就可以继续分析代码而无需程序员手动标注这个函数签名里的生命周期。
2. 失败例
来看第二个例子:
fn longest(x:str, y:str) - str {//...
}这个函数签名有两个引用输入返回类型也是引用。尝试用这3条规则
首先应用第一条规则——每个引用类型的参数都有自己的生命周期。这里有两个参数就有两个生命周期:
fn longesta, b(x:a str, y:b str) - str {//...
}由于有两个引用参数所以规则2不适用。
由于这个函数不是方法所以规则3不适用。
应用完这3条规则后发现返回值的生命周期仍然无法确定所以编译器就会报错。也就是说你必须显式声明生命周期。