rust学习十一.3、生命周期标记
生命周期,这是在"引用和借用“章节就提到的概念,大意是每个变量具有其作用域范围。所以,我个人更愿意理解为作用范围。 因为它不像java的变量那样和时间有较为明显的关联,毕竟java的变量会被GC销毁。
一、 生命周期注解概念引入
在原文中,作者是通过两个例子解释生命周期问题
fn main() {<br> let r; // ---------+-- 'a<br> // |<br> { // |<br> let x = 5; // -+-- 'b |<br> r = &x; // | |<br> } // -+ |<br> // |<br> println!("r: {r}"); // |<br>} // ---------+<br>fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a|
// | |
println!("r: {r}"); // | |
// --+ |
} // ----------+
作者给的图很清楚地展示了作用范围所导致的生命周期问题(作用范围问题)。
对于上图的第一种环境,在没有特殊处理的环境下,r是借不到到x的值(离开返回后x就会被销毁了)
另外一个例子
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
这个编译会发生错误,作者给出的大体解释是如许:编译器无法知道返回的是哪一个变量的引用,既然无法知道是哪一个,
那么就无法知道其作用范围,所以会报告错误(总之,道理都是rust说的)。为了避免这种环境,rust通过编译器提示我们需要添加符号来表示:无论哪一个参数都是一样的作用范围。
这个符号就是一种类似泛型的写法
例如上面的函数应该修改为:
fn largest_str<'a>(str1: &'a str, str2: &'a str) -> &'a str {
if str1.len() > str2.len() {
str1
} else {
str2
}
}
这个符号还是挺怪异的:fn largest_str str2.len() { str1 } else { str2 } } fn main() { let s1="good"; let s2="bad"; let result=largest_str(s1, s2); println!("{}和{}种较长的一个是{}", s1,s2,result); }
注解符号
<ul>必须用单引号(')开头
单引号后面通常跟上小写字母
&str{ s}fn main() { let s = student { name: "Tom", age: 18, }; println!("{:?}",s); let s1 = print_str(s.name);}例如上例,如果结构和枚举没有引入生命周期符号,那么编译都会错误,而函数print_str并不会错误。
对于函数print_str用书上给出的理由可以说得通,那么结构中又是什么意思了? 也许需要查看编译器代码才知道为什么。
但对外就不那么美好多了,幸亏编译器足够体贴。
2.2 是不是提示有问题?
这里直接给出就是书上的例子:
fn largest_str<'a>(str1: &'a str, str2: &'a str) -> &'a str {
if str1.len() > str2.len() {
str1
} else {
str2
}
}
fn main() {
let s1="good";
let s2="bad";
let result=largest_str(s1, s2);
println!("{}和{}种较长的一个是{}", s1,s2,result);
}
这里编译报错如下:
https://img2024.cnblogs.com/blog/1177268/202411/1177268-20241125173128525-605882904.png
这个会报错,使用前面所有权的知识,就能知道为什么,所以这应该是所有权的问题,而不应该提示“borrowed value does not live long enough”.
当出现让人迷糊的提示的时候,应该优先考虑是否所有权出现问题,而不是所谓的其他的生命周期。
上面的例子中,如果删除最有一句那么是不会报错的:result = largest_str(string1.as_str(), string2.as_str());
所以,逻辑上,这里调用larget_str不是问题,而是对result超出范围使用,因为result无法访问string2.
三、深入理解生命周期
即讨论以下几个问题:
[*]不同步伐结构中的生命周期定义
[*]如何判定是否需要定义申明周期定义
[*]可以省略的注解
[*]静态生命周期
部分问题,前面已经讨论过。
1、2、3问题其实都可以归纳为一个:到底什么环境下需要引入生命周期注解符号?
给出一个可以将就的答案:等编译器提示的时候再录入不迟。
3.1不同步伐结构中的生命周期定义
在以下几中步伐结构中,都可能需要引入:
[*]结构-如果有引用,则必须有
[*]枚举-如果有引用,则必须有
[*]方法/函数-需要看环境。一样平常如果只有一个参数可以考虑不要;大概如果可以或许明确知道总是和某一个参数相干也可以不要。
部分在前面的例子中,已经给出结论,此处示例略。
3.2省略的生命周期注解
#
struct student<'a>{
name: &'a str,
age: i32,
}
enum cars<'a>{
byd(&'a str),
}
fn print_str(s:&str)->&str{
s
}
fn main() {
let s = student {
name: "Tom",
age: 18,
};
println!("{:?}",s);
let s1 = print_str(s.name);
}上例中,本来是需要注解,但是rust团队厥后发现遵照一些特定规则的,可以不要,以为可以推定出来。
另有例如:
fn main() {
let s1="good";
let s2="bad";
let result=largest_str(s1, s2);
println!("{}和{}种较长的一个是{}", s1,s2,result);
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = largest_str(string1.as_str(), string2.as_str());
}
println!("The longest string is {result}");
}这是因为编译器可以推定返回的只和x有关。 这是属于有多个引用参数且返回引用类型的。
还是这个例子。虽然可以通过编译器推定出返回结果只和第一个参数有关,但是依然需要为第一个参数添加生命周期注解。这颇为迷惑,如果我们再看看下面这个代码:
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s;
}
}
&s[..]
}
这个是不需要的。
对于步伐员而言,如何比较简单地判定:
[*]如果有多个引用参数且返回也是引用,那么一样平常是需要的注解的
[*]如果只有一个引用参数,通常可以不要
如果实在还不可,就等着编译器提示吧。
3.3 规则
函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。
编译器采用三条规则来判定引用何时不需要明确的注解。
第一条规则实用于输入生命周期,后两条规则实用于输出生命周期。如果编译器查抄完这三条规则后仍旧存在没有计算出生命周期的引用,编译器将会停止并天生错误。这些规则实用于 fn 定义,以及 impl 块。
注意:这个规则只实用于函数/方法
第一条规则(输入、即用于参数)
编译器为每一个引用参数都分配一个生命周期参数。换句话说就是,函数有一个引用参数的就有一个生命周期参数:fn foo &'a i32。
第三条规则(输出、用于返回)
如果方法有多个输入生命周期参数而且此中一个参数是 &self 或 &mut self,阐明是个对象的方法 (method)(译者注:这里涉及 rust 的面向对象拜见 17 章),那么所有输出生命周期参数被赋予 self 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。
3.4静态生命周期
'static,其生命周期可以或许存活于整个步伐期间。所有的字符串字面值都拥有 'static 生命周期
let s: &'static str = "I have a static lifetime.";
作者提醒我们:应该谨慎使用,毕竟这些会在整个步伐运行期间占用内容。
通常而言,只要不是过分,也不应过于担心。看看java写的后台代码,那是一坨坨,一堆堆,一簇簇...的静态常量。
但java是不用关心性能(相对而言)。
对于rust中,该用还是用,不外分以至于影响性能即可。
3.5 同时引入生命周期标记和通用类型
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
} 这个例子重要告诉我们:如何引入
四、小结
<ol>为什么需要生命周期注解?重要原因是因为编译器无法推断出变量的作用范围,必须人工告知它们的作用范围是一致的。如果你不告诉它,让它在运行时处理,那么大费心机弄得所有权和生命周期就么有太大意义了
生命周期注解的作用就在于告知编译器:某些参数它们的生命周期是一样的,你放过我吧!
对于函数/方法而言,注解标记的必要性是因为引用类型参数过多导致的,而且是在返回类型也是引用的环境下
对于其它对象(结构体、枚举等),只要有引用类型,都必须定义生命周期注解标记
也有不需要添加注解环境,通常实用于只有一个引用参数,返回也是一个引用的环境。但是环境并不是只有这种。
‘static是一个静态生命周期标记,常见的字符串字面量都是如许的。注意使用
一个方法中如果又有通用类型,又有引用,那么还是可以写出来的的,例如
页:
[1]