《Beginning Rust From Novice to Professional》---读书随记(生命周期扩展)
Beginning Rust From Novice to Professional
Author: Carlo Milanesi
如果需要电子书的小伙伴,可以留下邮箱,看到了会发送的
Chapter 23 More About Lifetimes
Lifetime Elision
trait Tr {
fn f(x: &u8) -> &u8;
}
这个返回值就不需要标注生命周期,这时候默认在参数中选一个参数为参考,而这里只有一个参数
trait Tr {
fn f(b: bool, x: (u32, &u8)) -> &u8;
}
trait Tr {
fn f(x: &u8) -> (&u8, &f64, bool, &Vec<String>);
}
这些函数有一个特征,就是都可以找到唯一可用的生命周期
Lifetime Elision with Object-Oriented Programming
trait Tr {
fn f(&self, y: &u8) -> (&u8, &f64, bool, &Vec<String>);
}
这时候,有两个引用入参,那么前面那个约定当然就不起效了,但是,如果是入参有self
,那么默认就是使用的self
的生命周期,也就是等价于下面:
trait Tr {
fn f<'a>(&'a self, y: &u8) -> (&'a u8, &'a f64, bool, &'a Vec<String>);
}
也可以对选定的引用覆盖这类行为。比如说,下面的函数中,第二个返回值与y关联,第一个和第四个默认是和self关联
trait Tr {
fn f<'a>(&self, y: &'a u8) -> (&u8, &'a f64, bool, &Vec<String>);
}
The Need of Lifetime Specifiers for Structs
struct S {
_b: bool,
_ri: &i32,
}
let _y: S;
let x: i32 = 12;
_y = S { _b: true, _ri: &x };
上面的代码,是错误的,其实和函数的检查方式差不多,因为之前一开始的时候,我们的代码是写在一个块里面的,函数内部是一个块,当然结构体的内部也是一个块,所以它们的检查也是差不多的。看代码,S结构体内部有一个引用字段,那么S的声明是在_y这一行,然后初始化是在x之后,并且引用了x,很明显,我们是按照变量的声明顺序释放对象,所以_y的生命周期是比x的长,所以编译报错
struct S {
_b: bool,
_ri: &i32,
}
fn create_s(ri: &i32) -> S {
S { _b: true, _ri: ri }
}
// In application code:
fn main() {
let _y: S;
let x: i32 = 12;
_y = create_s(&x);
}
接着看一个复杂一点的例子,代码中,函数虽然没有直接返回引用,但是返回了一个包含引用的结构体的实例,我们首先从内部开始向外看,S中的引用的生命周期与ri关联,那么就函数的签名和主体部分来看,生命周期不需要标注,这部分是对的,但是视角来到main函数中的时候,我们只需要看create_s的函数签名,返回值的结果与x的生命周期关联,也就是说,_y的生命周期应该比x要小,但是很明显不对,_y先声明的,所以,编译报错
struct S {
_b: bool,
_ri: &'static i32,
}
fn create_s(ri: &i32) -> S {
static ZERO: i32 = 0;
static ONE: i32 = 1;
S {
_b: true,
_ri: if *ri > 0 { &ONE } else { &ZERO },
}
}
// In application code:
fn main() {
let _y: S;
let x: i32 = 12;
_y = create_s(&x);
}
然后看一个改良版的代码,这次,S的引用的生命周期标注是静态对象,那么其实这时候,函数create_s的返回值的生命周期和入参就没有关联了,把这个S当作是没有引用的结构体就可以了,因为内部的引用的生命周期与程序挂钩
Possible Lifetime Specifiers for Structs
实际上,对于结构的引用字段的生命周期,Rust编译器只允许两种可能性:
- 这样的字段只允许引用静态对象
- 这样的字段允许引用静态对象或尽管不是静态对象,但在整个结构对象之前存在并且比它寿命更长的对象
// In some library code:
struct S<'a> { _b: bool, _ri: &'a i32 }
fn create_s<'b>(ri: &'b i32) -> S<'b> {
S { _b: true, _ri: ri }
}
// In application code:
fn main() {
let x: i32 = 12;
let _y: S;
_y = create_s(&x);
}
_ri存储在结构体中,不需要检查“create_s”函数的主体,也不需要检查“S”结构体的字段列表;只要检查create_s函数的签名和S的签名,即在开括号之前的声明部分就足够了
Other Uses of Lifetime Specifiers
struct S<'a, T> {
b: &'a T
}
这是错误的,编译报错:"the parameter type T
may not live long enough"
这样做的原因是泛型类型T可以被包含引用的类型具体化,而这样的引用可能会导致生命周期错误。为了防止它们,编译器禁止这种语法。
- 由“T”表示的类型将不包含引用,或者它将只包含对静态对象的引用;
- 由“T”表示的类型可以包含对非静态对象的引用,其中必须指定其生命周期;
第一种:
struct S<'a, T: 'static> { b: &'a T }
let s = S { b: &true };
print!("{}", *s.b);
第二种:
struct S<'a, T: 'a> { b: &'a T }
let s1 = S { b: &true };
let s2 = S { b: &s1 };
print!("{} {}", *s1.b, *s2.b.b);
在第一行中,“T”类型参数被限制为“a”生命周期说明符,这意味着这种类型,无论它是什么,都可能包含一个引用,该引用借用了已经由该生命周期说明符注释的相同对象,即整个结构对象本身。
在第二行中,“S”结构体被实例化了,它隐式地指定“bool”为它的“T”类型参数。实际上,这种类型不包含任何引用,因此对于这一行,一个静态边界就足够了,如前面的示例程序所示。
但是在第三行中,“S”结构被实例化,隐式地指定S<bool>
作为它的“T”类型参数。实际上,这种类型确实包含一个非静态引用,因此对于这一行,静态边界是不够的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?