前言
阅读耗时2分钟
目录
一、生命周期
Rust
的每个引用都有自己的生命周期,所谓生命周期,就是指引用保持有效的作用域。
当引用的生命周期可能以不同的方式相互关联时,可以手动标注生命周期。
目标
生命周期的主要目标就是避免悬垂引用(dangling reference)
悬垂引用
fn main() {
let r;
{
let x = 5;
r = &x; //这里会报错
}//x作用域结束
println!("r: {}", r);
}
报错
error[E0597]: `x` does not live long enough
--> src\main.rs:17:13
|
17 | r = &x;
| ^^ borrowed value does not live long enough
18 | }
| - `x` dropped here while still borrowed
19 | println!("r: {}", r);
| - borrow later used here
x
的生命周期在 }
执行完后就结束了。而后面我们竟然还用了。这就是悬挂引用
为什么在编写代码时就会价差出来这个问题,会报错呢?或者说为什么编译就会出错呢?
这完全依赖于Rust的借用检查器,通过比较作用域来判断所有的借用是否合法。
如何修改呢?
let x = 5;
let r = &x;//这样就可以了
println!("r : {}", r};
被用于函数参数的生命周期
fn main() {
let string1 = String::from("abcdef");
let string2 = "efgi";
let result = longest(string1.as_str(), string2);
println!("longest string is {}", result);
}
fn longest(x: &str, y: &str) -> &str{
if(x.len() > y.len()) {
x //报错
}else{
y // 报错
}
}
上述报错,是因为返回值的生命周期,不知道是x
的还是y
的,可以手动尝试run
一下,编译器会提示怎么修改
下面按编译器提示修改
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str{
if(x.len() > y.len()) {
x //报错
}else{
y // 报错
}
}
加了一个生命周期的标注 'a
代表 x
、y
和返回值是一样的生命周期
二、生命周期标注
- 生命周期的标注不会改变引用的生命周期长度
- 描述多个引用的生命周期的关系,但不影响生命周期
标注定义
&'a test// 引用&test 到标注只要加('a空格) a换成其它小写字母都可以
函数签名生命周期标注
//方法后面要加声明
//x和y和的生命周期必须不能短于'a, 函数返回值也不能短于 'a
//'a 的生命周期其实结果是 x 和 y中最短的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str{
}
比如换一种写法
fn main() {
let string1 = String::from("abcdef");
let result;
{
let string2 = String::from("efg");
result = longest(string1.as_str(), string2.as_str());、//这块会报错
}
println!("longest string is {}", result);
}
报错原因就是因为result
取的是string1
和 string2
中最短的生命周期,result
持有的借用 出了 }
就失效了。
再换一种写法看看
fn longest<'a>(x: &'a str, y: &'a str) -> &str{
let result = String::from("abc");
result.as_str() //这里报错
}
为什么报错?因为result.as_str()
在函数方法执行完,生命周期就结束了,这块引用就被清理掉了,然后又作为引用借用返回给外部调用方。
可以通过移交所有权来修改
fn longest<'a>(x: &'a str, y: &'a str) -> String{
let result = String::from("abc");
result
}
从上面可以看出生命周期的定义类似泛型,就是泛型的一种方式,所以可以和泛型一起用
fn longest_t<'a, T>(x: &'a str, y: &'a str, ann:T) -> &'a str
where T:Display
{
println!("Ann is {}", ann);
if x.len() > y.len(){
x
}else{
y
}
}
struct定义中的生命周期标注
struct GoToWork<'a>{
part: &'a str, //part的生命周期必须要比GoToWork实例生命周期长
}
fn main(){
let string = String::from("testtest");
let i = GoToWork{
part:string.as_str()
};
}
平时正常写的时候不会提示生命周期问题,原因就是我们用的时候,例如part
生命周期比实例还长。
方法定义中的生命周期标注
struct GoToWork<'a>{
part: &'a str, //part的生命周期必须要比GoToWork实例生命周期长
}
impl<'a> GoToWork<'a>{
fn level(&self) -> i32{
3
}
fn return_test(&self, item: &str) -> &str{
self.part
}
}
impl
块内的方法签名中
- 引用必须绑定于
struct
字段引用的生命周期,或者引用是独立的 - 生命周期省略规则使得方法中的生命周期标注不是必须的
三、生命周期省略
不声明生命周期也OK
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[..]
}
等价于
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[..]
}
其原因是Rust
团队觉得每个方法或者stuct
都写生命周期很繁琐,就做了一套统一的处理,直接写入编译器中。
四、生命周期规则
- 输入生命周期
- 生命周期在函数/方法的参数上
- 输出生命周期
- 生命周期在函数的返回值上
生命周期规则
规则1应用于输入生命周期,规则2、3应用于输出生命周期,如果编译器应用完3个规则后,仍然无法确定生命周期的引用,就会报错。这些规则适用于 fn
定义和 impl
块。
- 规则1
- 每个引用类型的参数都有自己的生命周期
- 规则2
- 如果只有1个输入生命周期参数,那么该生命周期被赋给所有输出生命周期
- 规则3
- 如果有多个输入生命周期参数,但其中一个是
&self
或者&mut self
(是方法),那么self
的生命周期会被赋给所有输出生命周期
- 如果有多个输入生命周期参数,但其中一个是
1. 一个入参的生命周期标注过程
我们看之前的first_word
方法
fn first_word(s:&str) -> &str
应用完规则1后变成
fn first_word<'a>(s:&'a str) -> &str
再应用规则2
fn first_word<'a>(s:&'a str) -> &'a str
周期应用完毕,编译器正常运行
2. 多个入参的生命周期标注过程
fn longest(x: &str, y: &str) -> &str
应用规则1
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str
然后不符合规则2,因为是多个参数
再看规则3,不是方法,没有self
参数,不符合规则,不能确定返回值的生命周期。编译器报错,需要手动标注生命周期。
如果我们再看之前 impl
块的 return_test
方法,它就符合规则1 和 规则 3,所以生命周期标注省略。
五、静态生命周期
通过关键字 'static
声明,代表整个程序持续的时间。所有的字符串字面值都有 'static
生命周期。
注意,使用了这个关键字,代表的是这个引用在程序整个生命周期内都存活。
let s:&'static str = "I want to go to work!";