生命周期
简介:
Rust每个引用都有其生命周期,生命周期指的是让引用保持有效的作用域,大多数情况下生命周期是隐式的,是可推断的。
当引用的生命周期以不同方式互相关联的时候:必须手动标识生命周期。
如下所示
fn main() {
let string1 = String::from("ABCD");
let string2 = "xyz";
let result = longest(&string1, &string2);
println!("The longest string is {}",result);
}
fn longest(x:&str,y:&str) ->&str{
//在这个地方就会报no lifetime specific
if x.len() > y.len() {
//因为编译器无法确定返回值的生命周期
x
} else {
y
}
}
因此我们做以下修改来手动表示生命周期
fn main() {
let string1 = String::from("ABCD");
let string2 = "xyz";
let result = longest(&string1, &string2);
println!("The longest string is {}",result);
}
fn longest<'a>(x:&'a str,y:&'a str) ->&'a str{
//在此处手动声明
if x.len() > y.len() {
x
} else {
y
}
}
目的:
生命周期的主要目标就是为了避免悬垂引用。(也就是空指针,我们都知道空指针会导致数据的错误,目前正在使用的内存,不可访问的内存遭到访问导致程序崩溃)
//example
fn main() {
let r;
{
let x =5;
r = &x;//此处的语句是报错的
}
println!("r: {}",r)
}
以上代码中出现的问题主要是r的生命周期是要长于r的引用的,也就是说x在{}结束的时候已经被释放内存,r此时指向了一个空的目标,这份内存已经被回收并可能分配给其他线程,导致了悬空指针。
生命周期的标注语法
简介:
生命周期的标注不会改变引用的生命周期长度,当指定了泛型生命周期参数,函数可以接受任何带有生命周期的引用,生命周期的标注描述了多个引用的生命周期间的关系,但不影响生命周期
语法:
生命周期参数:通常以 '开头,并采用全小写,小写非常短,比如’a。
生命周期标注的位置:通常在引用的&之后,并使用空格将标注和引用类型分开
&i32 //一个引用
&’a i32 //带有显示生命周期的可变引用
&'a mut i32 //带有显式生命周期的可变引用
函数签名中的生命周期:
看以下代码:
fn main() {
let string1 = String::from("ABCD");
let string2 = "xyz";
let result = longest(&string1, &string2);
println!("The longest string is {}",result);
}
fn longest<'a>(x:&'a str,y:&'a str) ->&'a str{
//这里返回值的生命周期取得是传入的引用中生命周期比较短的那一个
if x.len() > y.len() {
x
} else {
y
}
}
以上代码是可以通过编译的,但是如果我们对函数进行以下修改
use std::result;
fn main() {
let string1 = String::from("ABCD");
let result;
{
let string2 = String::from("XYZ");
result = longest(string1.as_str(), string2.as_str());
}//string2从这里开始就已经死去了
println!("The longest string is {}",result);
}
fn longest<'a>(x:&'a str,y:&'a str) ->&'a str{
if x.len() > y.len() {
x
} else {
y
}
}
我们发现以上代码始终无法通过编译,因为result所接受的那个函数所取得的生命周期取最小值string2,所以这个函数在打印时string2的内存已被回收,打印函数访问时已经消失。
深入理解生命周期
指定生命周期参数的方式依赖于函数所做的事情,从函数返回引用时,返回类型的生命周期参数需要其中一个参数的生命周期匹配。
fn main(){
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("the longest string is {}",result);
}
fn longest<'a>(x:&'a str,y:&str) ->&'a str{
//此处的函数返回值的生命周期显然只和x有关
x
}
上述代码中我们就只需要对x的生命周期进行显式的说明
如果返回的引用没有指向任何参数,那么他只能引用函数内创建的值:
这就是悬垂引用:该值在函数结束时就走出了作用域
下列函数在调用时返回了result的一个切片,但是该函数的result在函数出栈时就已经被回收内存,所以返回的切片已经悬空,会发生编译错误。
fn longest<'a>(x:&'a str,y:&str) ->&'a str{
let result = String::from("ABC");
result.as_str()
}
如果进行以下修改
fn longest<'a>(x:&'a str,y:&str) ->String{
let result = String::from("ABC");
result
}
将本该回收的作用域的所有权交给了main函数中的result则能够通过编译。
struct定义中的生命周期标注
struct里可以包括
·自持有的类型
·引用:需要在每个引用上添加生命周期标注
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main(){
let novel = String::from("call me ishmael.some years ago..");
let first_sentence = novel.split('.')
.next()
.expect("Could not found a '.'");
let i = ImportantExcerpt{
part:first_sentence
};
}
注意:切片就是引用
生命周期的省略
我们知道:
1.每个引用都有生命周期
2.需要为使用生命周期的函数或者struct指定生命周期参数
但是也存在例外,也就是所谓的生命周期的省略
fn first_word<'a>(s:&'a str)->&'a str {
let bytes = s.as_bytes();
for (i,&item) in bytes.iter().enumerate() {
if item == b' '{
return &s[0..i];
}
}
&s[..]
}
上述代码对引用声明了生命周期,但是在这种情况下,生命周期的声明是可以省略的,也就是以上代码和以下代码是等价的。
fn first_word(s:&str)->&str {
let bytes = s.as_bytes();
for (i,&item) in bytes.iter().enumerate() {
if item == b' '{
return &s[0..i];
}
}
&s[..]
}
输入生命周期和输出生命周期
生命周期在函数/方法的参数中:输入生命周期
生命周期在函数/方法的返回值中:输出生命周期
生命周期省略的三个规则
1.每个引用类型的参数都有自己的生命周期
2.如果只有一个生命周期参数,那么该生命周期被赋给所有的输出生命周期参数
3.如果有多个输入生命周期参数,但其中一个是&self或者&mut self,那么self的生命周期会被赋给所有的输出生命周期参数
方法定义中的生命周期标注
struct字段的生命周期名:
在impl后面声明
在struct名后使用
这些生命周期是struct类型的一部分
impl块内的方法签名中:
应用必须绑定于struct字段引用的生命周期,或者引用是独立的也可以
生命周期省略规则经常使得方法中的生命周期标注不是必须的
静态生命周期
static是一个特殊的生命周期:整个程序的持续时间,因此在使用static的时候需要慎重考虑是否需要这个引用在整个程序的生命周期内存活。