起因是自己在学习rust,看了几遍官方文档,对生命周期的使用还是迷迷糊糊的,因此在bilibili上找相关视频,果然发现一位宝藏up主"这周你想干啥"讲解的lifetime课程,看了2遍受益匪浅,终于是理解了rust中的生命周期含义和用法,但是看视频是很消耗时间的,并且课程附带了很多相关文档链接,资料比较分散,因此想将核心要点记录成笔记,方便查阅。
先附带上bilibili观看链接
在自己学习过程中,觉得观看该视频可以先看看下面连接内容,强烈推荐阅读一遍,这样再跟着视频学习就能更深入的理解了。可以使用浏览器沉浸式翻译插件进行中英文阅读
Common Rust Lifetime Misconceptions(常见的Rust生命周期误解)
其次可以跟着lifetimeKata中的测试题先练习一遍
一 什么是生命周期
初看《Rust 程序设计语言》学习rust,里面的一些知识点并没有真是理解,好像只知道"是那么一回事",在看视频学习的时候,发现自己并没有理解什么是"生命周期",连概念都没有深刻的理解。在此将官方文档中的定义摘抄下来。
生命周期:Rust 中的每一个引用都有其 生命周期 (lifetime),也就是引用保持有效的作用域。也就是说有引用才有生命周期。
在rust中有两类引用
- & T:不可变引用,同一时刻可以有多个不可变引用(有种特殊情况,内部可变性)
- &mut T:可变引用,同一时刻只能有一个可变引用
特别要强调一点,T类型是包含& T和&mut T的
如何学习生命周期:理解依赖+类比泛型 (可以把引用理解为是一个依赖)
二 生命周期标注
生命周期标注的作用:用来描述多个引用生命周期相互之间的关系。 为什么需要标注:因为有时候编译器无法帮我们判断,所以需要手动写生命周期标准。
rust
fn main(){
let a = 1;
let my_num = complex_function(&a);
println!("{my_num}");
}
fn complex_function(a:&i32) ->&i32{
let b = 2;
max_of_refs(a,&b)
}
// 这里函数入参和返回值的生命周期都是'a,说明入参和出参依赖数据的生存时间是一样长的。
//但是在complex_function函数里b的生命周期和函数体一样长
//而该函数的返回值的生命周期明显要长于b的生命周期的
// println!("{my_num}");在这里有使用到complex_function函数的返回值
//所以当编译器在调用max_of_refs函数时这里就会报错,因为b的生命周期不够长
fn max_of_refs<'a>(a:&'a i32,b:&'a i32) -> &'a i32{
if *a > *b{
a
} else {
b
}
}
问题修复
rust
fn main(){
let a = 1;
let my_num = complex_function(&a);
println!("{my_num}");
}
fn complex_function(a:&i32) ->&i32{
let b = 2;
max_of_refs(a,&b)
}
fn max_of_refs<'a,'b>(a:&'a i32,b:&'b i32) -> &'a i32{
a
}
还有一个要点,当在max_of_refs函数上将a,b都标注为'a时,还是会报错,因为这里生命周期标注的意思是,b的生命周期和a的生命周期一样长,但实际情况是b的生命周期只有8~10行。
在这个案例中可以看到,虽然函数的逻辑没有问题,但是编译还是会报错,原因就是rust编译器通过查看max_of_refs函数声明来进行判断的
rust
fn main(){
let a = 1;
let my_num = complex_function(&a);
println!("{my_num}");
}
fn complex_function(a:&i32) ->&i32{
let b = 2;
max_of_refs(a,&b)
}
fn max_of_refs<'a>(a:&'a i32,b:&'a i32) -> &'a i32{
a
}
三 生命周期在函数上的省略
1. lifetime在函数上的省略规则
在函数或方法上生命周期省略主要有三条:
- 每一个是引用的参数都有它自己的生命周期参数(要注意这里说的是引用,如果入参不是引用,是没有生命周期的)
- 如果只有一个输入生命周期参数(也就是只有一个引用的参数,但可能会有多个无引用参数),那么它被赋予所有输出生命周期参数
- 如果方法有多个输入生命周期参数并且其中一个参数是
&self
或&mut self
,说明是个对象的方法(method),那么所有输出生命周期参数被赋予self
的生命周期
更详细以及示例可以参考官方文档,生命周期省略
也可以参看rust参考手册上的生命周期省略内容更详细
举个反向的例子,对于函数上引用参数的生命周期省略,如果超过了上面三条规则,编译器就会判断为错误,这时候就需要我们手动去标注了,以便告诉编译器我们的想法,比如入参与出参,或者多个入参之间的生命周期关系。
rust
fn main() {
// 无法推断,因为没有可以推断的起始参数。
fn get_str() -> &str; // 非法
// 无法推断,这里无法确认输出的生命周期类型参数该遵从从第一个还是第二个参数的。
fn frob(s: &str, t: &str) -> &str; // 非法
}
再举一个容易出错的示例
rust
fn main() {
struct S<'r>{
slice:&'r [u8],
}
impl<'r> S<'r>{
// 在这里入参是一个可变引用,但是出参是一个不可变引用
//又因为入参和出参的生命周期是一样的
//那么说明可变参数的借用时间会和不可变引用的出参一样长
//但又根据理论同一时刻只能有一个可变引用
//那么如果出现调用2次该函数的情况下,这里就会报错。
fn as_slice_mut<'a>(&'a mut self) -> &'a [u8]{
// todo
}
}
}
这里还有一个概念叫做重借用(re-borrow),可以看一下downgrading mut refs to shared refs is safe帮助理解,当然后面还会有详细的re-borrow内容。
2. 函数上的可变引用
下面的示例,在运行时会报错,什么原因呢?insert_value函数上的生命周期标注,根据函数上生命周期省略规则,每一个引用,都有自己的生命周期,因此添加3个生命周期标注,但在函数体中可以看出,'b是依赖'c的,'c的生命周期要比'a上才可以,即'c:'b,但实际上'c的生命周期只有57行,而'b的生命周期是38行。
rust
fn main(){
let x = 1;
let mut my_vec = vec![];
{
let y = 2;
insert_value(&mut my_vec, &y);
}
}
fn insert_value<'a,'b,'c>(my_vec:&'a mut Vec<&'b i32>,value:&'c i32){
my_vec.push(value);
}
修复:可以将value参数的生命周期设置的和'b一样长,这样就可以通过了
rust
fn main(){
let x = 1;
let mut my_vec = vec![];
{
let y = 2;
insert_value(&mut my_vec, &y);
}
}
fn insert_value<'a,'b>(my_vec:&'a mut Vec<&'b i32>,value:&'b i32){
my_vec.push(value);
}
再举个例子,以加深理解 分析下面的代码,在insert_value函数上,3个参数的生命周期都标注为'a,说明三个参数的引用生命周期是一样长的,也就是说函数里的可变引用&'a mut Vec被延长到和Vec<&'a i32>中的引用一样长,而my_vec向量的生命周期是从2~10行的,所以两次调用insert_value,会导致报同一时刻调用多次可变引用错误
rust
fn main() {
let mut my_vec:Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1);
insert_value(&mut my_vec, &val2);
}
fn insert_value<'a>(my_vec:&'a mut Vec<&'a i32>,value:&'a i32){
my_vec.push(value);
}
修复:将insert_value函数中my_vec参数的引用生命周期和其他两个解绑,这样就可以告诉编译器,my_vec可变引用调用完之后就结束了,那么多次调用就不会报错了。
rust
fn main() {
let mut my_vec:Vec<&i32> = vec![];
let val1 = 1;
let val2 = 2;
insert_value(&mut my_vec, &val1);
insert_value(&mut my_vec, &val2);
}
fn insert_value<'a,'b>(my_vec:&'a mut Vec<&'b i32>,value:&'b i32){
my_vec.push(value);
}
四 struct和enum生命周期标注
struct、enum上有了生命周期,impl方法也要有对应的声明 学会了函数生命周期标注,这里就比较简单了,没有什么内容可以增加的。
五 static和_特殊生命周期[这里缺少文档链接]
- &'static T代表能在整个程序运行期间存活,比如&'static str
- '_ 意思是让编译器帮忙推导生命周期,在下面这些地方会用到
- impl声明 帮助简化
- 输入/返回 一个需要生命周期的类型时
- Trait Object包含生命周期 这里有一个容易出错的概念&'static T 和 T:'static是不一样的,T:'static是包含&'static T的,更详细的可以查看, &'a T and T: 'a are the samething
示例:在下面的例子中,会报错,为什么呢?因为foo行数的生命周期标注入参和返回值都是'a,说明返回值的生命周期是依赖input变量的,但是input变量的生命周期只有4~6行,所以在第七行使用x值时就报错了。
rust
fn main() {
let x;
{
let input = String::from("Hello, world!");
x = foo(&input);
}
println!("{}", x);
}
fn foo<'a>(_input: &'a str) -> &'a str{
"abc"
}
修复:因为foo函数的返回值是"abc"是不依赖入参的,并且是&'static str类型,是存活在整个程序运行阶段的,所以将返回值的生命周期标注为'static就可以正常运行了
rust
fn main() {
let x;
{
let input = String::from("Hello, world!");
x = foo(&input);
}
println!("{}", x);
}
fn foo<'a>(_input: &'a str) -> &'static str{
"abc"
}
六 生命周期绑定和型变
参考资料:1.The rust Reference、2.The Rustonnomicon
1.什么是型变?
首先要理解什么是型变?专业名称叫做"里氏替换原则",说道这里想起来学习ts时之记住一个"传父返子",并没有太理解是怎么一回事,更详细的解释可以看视频,型变
学习之后我自己的理解是,如果A是B的子类,协变的概念就是,有一个容器Box里添加A元素,另一个Box里添加B元素,那么Box<A>就是Box<B>的子类。逆变则刚好相反,Box<B>是Box<A>的子类,不变就更好理解了,就是彼此之间没有关系。
2.生命周期bound
在rust中如何标注生命周期子类型
- 'a:'b代表'a是'b子类,也就是'a可以替换'b,'a的生命时间要比'b长
一些特殊的用法:
- 'a:'b和T:'a(这里需要注意的是,T:'a,代表的意思是接受任何可以超出'a的生命周期的类型,T,&'a T, &'a mut T)
- &'static T和T:'static
关于&'static T和T:'static的理解,需要注意的是T:'static是包含&'static T类型的
rust
fn main(){
let my_string = String::from("Hello World");
foo(&my_string);// 运行报错,因为&my_string生命周期不是'static的
// bar(&my_string); //正常运行,因为my_string是String类型,是所有者
}
// &'static T意思是不可变引用必须是和静态生命周期相同
fn foo<T>(_input:&'static T){
println!("foo");
}
// T代表所有的类型,所有者,&T和&mut T,如果T是引用的那么必须和static生命周期一样长
// 如果是所有者的话,就没有约束
// 也就是说T:'static只约束引用
fn bar<T:'static>(_input:&T){
println!("bar");
}
3.rust中的型变
这里先从上面参考资料里截取一写要点,便于理解
-
If we remember from the above examples, it was ok for us to treat
&'a T
as a subtype of&'b T
if'a <: 'b
, therefore we can say that&'a T
is covariant over'a
. -
A struct, informally speaking, inherits the variance of its fields. If a struct
MyType
has a generic argumentA
that is used in a fielda
, then MyType's variance overA
is exactlya
's variance overA
.However if
A
is used in multiple fields:- If all uses of
A
are covariant, then MyType is covariant overA
- If all uses of
A
are contravariant, then MyType is contravariant overA
- Otherwise, MyType is invariant over
A
- If all uses of
示例说明:因为U在两处都有使用,既有协变的,又有不变的,所以整个结构体对于U来说是不变的
rust
fn main() {
use std::cell::UnsafeCell;
struct Variance<'a, 'b, T, U: 'a> {
x: &'a U, // 这让 `Variance` 在 'a 上是协变的, 也让在 U 上是协变的, 但是后面也使用了 U
y: *const T, // 在 T 上是协变的
z: UnsafeCell<&'b f64>, // 在 'b 上是不变的
w: *mut U, // 在 U 上是不变的, 所以让整个结构体在 U 上是不变的
}
}
- 结构体(
struct
)、枚举(enum
)、联合体(union
)和元组(tuple)类型上的型变关系是通过查看其字段类型的型变关系来决定的。 如果参数用在了多处且具有不同型变关系的位置上,则该类型在该参数上是不变的。 - 关系图
示例说明,在下面这个示例里,foo函数中因为标注了'long:'short说明'long是'short的子类,那么根据上面摘抄的协变规则,对于Foo结构体整体类型来说Foo<'long>就是Foo<'short>的子类,所以下面执行时能够成功的
rust
use std::marker::PhantomData;
fn main() {}
struct Foo<'r>{
_phantom:PhantomData<&'r ()>,
}
fn foo<'short,'long:'short>(
mut short_foo:Foo<'short>,
mut long_foo:Foo<'long>
){
short_foo = long_foo;
}
那么如果_phantom更改为PhantomData<fn(&'r ())>类型的话,函数的入参是逆变的,所以整个结构体的类型也是逆变的,所以还按照short_foo = long_foo;就会报错。
rust
use std::marker::PhantomData;
fn main() {}
struct Foo<'r>{
_phantom:PhantomData<fn(&'r ())>,
}
fn foo<'short,'long:'short>(
mut short_foo:Foo<'short>,
mut long_foo:Foo<'long>
){
//short_foo = long_foo; //报错
long_foo = short_foo; //正常
}
再次修改代码,如下面内容,这个时候无论是谁给谁赋值都会报错,因为&mut T是不可变的,short_foo和long_foo两者之间是没有关系的,因为无法正常赋值。
rust
use std::marker::PhantomData;
fn main() {}
struct Foo<'r>{
_phantom:PhantomData<&'r ()>,
}
fn foo<'short,'long:'short>(
mut short_foo:&mut Foo<'short>,
mut long_foo:&mut Foo<'long>
){
short_foo = long_foo;
long_foo = short_foo;
}
从这些示例里可以看出,rust类型中是没有子类关系和概念的,但是通过生命周期标注的关系,可以使其拥有型变的使用。
七 生命周期re-borrow[缺少文档链接]
- &T实现了Copy,&mut T没有实现 Copy &T因为有Copy,所以编译器能够帮助我们去Copy,但是&mut T没有Copy的实现,所以只有move和re-borrow
rust
fn main(){}
struct S;
fn f1sr<'b,'a>(rb:&'b &'a S) -> &'b S{
// &'b &'a S => 'a:'b 引用'b是'a的引用,所以说明'a的生存时间要长于'b
// &'b T => T:'b
// *rb=>&'c S 这里因为&'c S是从&'b &'a S解引用得来的,可以推出'c:'b,有因为&T是可以Copy的,所以&'c S ~ &'b S是可行的
*rb
}
fn f2sr<'b,'a>(rb:&'b &'a mut S) -> &'b S{
*rb // &'c mut S 这里将*rb解开的话相当于这个样式
// &'c mut S => 'a:'b 推导出'c:'b
// 对于为什么'c:'b 可以这么理解:
// 因为在&'c mut S是从&'b&'a mut S中解引用来的,这也就意味着&'b &'c mut S是合法的
// 所以就退出'c:'b 一个长的返回一个短的,是可以的
// 将一个可变引用转变成不可变引用也是可以的
// 但是将不可变引用转变成可变引用是不可以的
}
fn f3sr<'b,'a>(rb:&'b mut &'a S) -> &'b S{
*rb // &'c S
// 因为&'c T的生命周期是可被copy的 copy成&'b也没有关系
// 可变引用的解引用 就会涉及到re-borrow
}
fn f4sr<'b,'a>(rb:&'b mut &'a mut S) -> &'b mut S{
*rb // &'c mut S
}
fn f2sm<'b,'a>(rb:&'b &'a mut S)->&'b mut S{
*rb // 解引用 得到 &'c mut S 'c:'b
// 因为&'b &'a mut S是可以copy多份的,所以不允许多次解引用获取里面的可变引用
// 修复的话可以将入参更改为rb:&'b mut &'a mut S
}
// 有可变引用 就会涉及到生命周期
fn f2lr<'b,'a>(rb:&'b &'a mut S) -> &'a S{
*rb // &'c mut S => 'c :'b, 'a:'b
// 可变引用转变成不可变引用没有问题
// 但是 'c 和 'a没有任何关系,并且不能被copy 所以这里会报错
}
// 这样修改 才能通过
// fn f2lr<'b:'a,'a>(rb:&'b &'a mut S) -> &'a S{
// *rb // &'c mut S => 'c :'b, 'a:'b 同时 'b:'a 那么就说明 'c也是'a的子集
// }
fn f3lr<'b,'a>(rb:&'b mut &'a S) -> &'a S{
*rb // => &'c S
// 都是不可变引用,虽然'c和'a没有关系
// 但是 &'a T不可以类型是可以被copy的
// 所以这里没有问题
}
// 报错 不可变引用不能变成 可变引用
fn f1lm<'b,'a>(rb:&'b &'a S) -> &'a mut S{
*rb
}
// 报错
fn f2lm<'b,'a>(rb:&'b &'a mut S) -> &'a mut S{
*rb // => &'c mut S 不能通过可变引用来获取一个可变引用
}
// 报错 修复fn f4lm<'b:'a,'a>
fn f4lm<'b,'a>(rb:&'b mut &'a mut S) -> &'a mut S{
*rb
}
- 生命周期中的re-borrow
举两个示例,来说明什么是重借用
rust
fn main(){
let mut i = 42;
let x = &mut i;
let y:&mut i32 = x; // let y:&mut i32 = &mut *x; 这里就是重引用
*y = 43;
println!("y = {}",*y);
*x = 44;
println!("x = {}",*x);
}
rust
fn main(){
let mut i = 42;
let x = &mut i;
// &mut T 是没有copy的
change_i(x);
println!("i = {}",x);//在这一样还可以使用x,说明上面函数入参是reborrow的
*x = 44;
println!("i = {}",x);
}
// 入参相当于 let y:&mut i32 = &mut *x 这里也是reborrow的应用
fn change_i(mut_i32:&mut i32){
*mut_i32 = 43;
}
七 trait object生命周期规则
1.什么是trait object
在这里就要多说一句了,The Rust Reference中文文档翻译及时性没有那么高,如果有点概念不太理解时,可以看看英文文档,在找trait object的概念就就发现了中英文不一样的地方 摘抄一段要点:
Trait objects are written as the keyword dyn
followed by a set of trait bounds, but with the following restrictions on the trait bounds. All traits except the first trait must be auto traits , there may not be more than one lifetime , and opt-out bounds (e.g. ?Sized
) are not allowed. Furthermore, paths to traits may be parenthesized. 核心要点就是dyn Trait后面只能添加可自动的trait,如Send/Sync等,并且只能添加一个生命周期标注 示例:一下都是trait object
dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static
dyn Trait + Send + 'static
- `dyn Trait +
dyn 'static + Trait
.dyn (Trait)
2.默认的 trait对象的生命周期
摘抄The Rust Reference内容:
当 trait对象的生命周期约束被完全省略时,会使用默认对象生命周期约束来替代上面定义的生命周期类型参数省略规则。但如果使用 '_
作为生命周期约束,则该约束仍遵循函数的省略规则。
如果将 trait对象用作泛型类型的类型参数,则首先使用此容器泛型来尝试为此 trait对象推断一个约束(来替代那个假定的生命周期)。
- 如果存在来自此容器泛型的唯一约束,则该约束就为此 trait对象的默认约束
- 如果此容器泛型有多个约束,则必须指定一个约显式束为此 trait对象的默认约束
如果这两个规则都不适用,则使用该 trait对象的声明时的 trait约束:
- 如果原 trait 声明为单生命周期约束,则此 trait对象使用该约束作为默认约束。
- 如果
'static
被用做原 trait声明的任一一个生命周期约束 ,则此 trait对象使用'static
作为默认约束。 - 如果原 trait声明没有生命周期约束,那么此 trait对象的生命周期会在表达式中根据上下文被推断出来,在表达式之外直接用
'static
。
rust
fn main() {
// 对下面的 trait 来说,...
trait Foo { }
// 这两个是等价的,就如 `Box<T>` 对 `T` 没有生命周期约束一样
type T1 = Box<dyn Foo>; //此处的 `T1` 和 `Box<T>` 都是容器泛型
type T2 = Box<dyn Foo + 'static>;
// ...这也是等价的
impl dyn Foo {}
impl dyn Foo + 'static {}
// ...这也是等价的, 因为 `&'a T` 需要 `T: 'a`
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);
// `std::cell::Ref<'a, T>` 也需要 `T: 'a`, 所以这俩也是等价的
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
// 对下面的 trait 来说,...
trait Bar<'a>: 'a { }
// ...这两个是等价的:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// ...这俩也是等价的:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
}
八 impl trait 生命周期规则
- impl trait作为返回值自动捕获所有范围内的类型和生命周期参数
摘抄RFC3498: Under this RFC, when an associated function or method in a trait definition contains in its return type a return position impl Trait
in trait (RPITIT), the impl of that item may capture in the returned opaque type, in all editions, all trait input type and lifetime parameters, all type and lifetime parameters present in the Self
type, and all type and lifetime parameters in the associated function or method signature.
When such an associated function or method in a trait definition provides a default implementation, the opaque return type will automatically capture all trait input type and lifetime parameters, all type and lifetime parameters present in the Self
type, and all type and lifetime parameters in the associated function or method signature.
In trait impls, return position impl Trait
(RPIT), in all editions, will automatically capture all type and lifetime parameters from the outer impl and from the associated function or method signature. This ensures that signatures are copyable from trait definitions to impls.
For example:
rust
#![feature(return_position_impl_trait_in_trait)]
trait Trait<'t> {
fn foo<'f>(self, x: &'t (), y: &'f ()) -> impl Sized;
// ^^^^^^^^^^
// Method signature lifetimes, trait input lifetimes, and
// lifetimes in the Self type may all be captured in this opaque
// type in the impl.
}
struct Foo<'s>(&'s ());
impl<'t, 's> Trait<'t> for Foo<'s> {
fn foo<'f>(self, x: &'t (), y: &'f ()) -> impl Sized {
// ^^^^^^^^^^
// The opaque type captures:
//
// - `'f` from the method signature.
// - `'t` from the outer impl and a trait input lifetime.
// - `'s` from the outer impl and the Self type.
(self, x, y)
}
}
- async fn 返回匿名impl Future + 参数生命周期
摘抄RFC3498: Under this RFC, when an associated function or method in a trait definition is an async fn
in trait (AFIT), the impl of that item may capture in the returned opaque type, in all editions, all trait input type and lifetime parameters, all type and lifetime parameters present in the Self
type, and all type and lifetime parameters in the associated function or method signature.
When such an associated function or method in a trait definition provides a default implementation, the opaque return type will automatically capture all trait input type and lifetime parameters, all type and lifetime parameters present in the Self
type, and all type and lifetime parameters in the associated function or method signature.
In the trait impls, AFIT will automatically capture all type and lifetime parameters from the outer impl and from the associated function or method signature. This ensures that signatures are copyable from trait definitions to impls.
This behavior of AFIT will be parsimonious with the current stable capture behavior of async fn
in inherent impls.
For example:
rust
#![feature(async_fn_in_trait)]
trait Trait<'t>: Sized {
async fn foo<'f>(self, x: &'t (), y: &'f ()) -> (Self, &'t (), &'f ());
// ^^^^^^^^^^^^^^^^^^^^^^
// Method signature lifetimes, trait input lifetimes, and
// lifetimes in the Self type may all be captured in this opaque
// type in the impl.
}
struct Foo<'s>(&'s ());
impl<'t, 's> Trait<'t> for Foo<'s> {
async fn foo<'f>(self, x: &'t (), y: &'f ()) -> (Foo<'s>, &'t (), &'f ()) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// The opaque type captures:
//
// - `'f` from the method signature.
// - `'t` from the outer impl and a trait input lifetime.
// - `'s` from the outer impl and the Self type.
(self, x, y)
}
}
- 一开始不理解捕获参数类型是什么意思,找到了一个示例,添加在这里帮助理解
rust
fn foo<'a, T>(x: &'a T) -> impl Sized { x }
// ^^^^^^^^^^
// ^ Captures `'a` and `T`.
练习题:
- dyn trait 和 impl trait的区别
rust
fn main(){}
trait Foo{}
impl Foo for &'_ str{}
// impl Foo会捕获T类型的生命周期,如果T是一个引用的话
// 正常运行
fn f1<T:Foo>(t:T) -> Box<impl Foo>{
Box::new(t)
}
// 根据之前学到的知识,下面函数展开是这样的:
// fn f2<T:Foo>(t:T) -> Box<dyn Foo + 'static>
// 所以这个函数运行会报错,因为如果T是&T那么生命周期长度无法确定与'static一样长
fn f2<T:Foo>(t:T) -> Box<dyn Foo>{
Box::new(t)
}
// 修复:fn f2<T:Foo+'static>(t:T) -> Box<dyn Foo>
// impl trait作为返回值时,是可以捕获参数中的生命周期和类型的
// 展开如下
// fn f3<'a>(s:&'a str) -> Box<impl Foo+'a>
// 正常运行
fn f3(s:&str) -> Box<impl Foo>{
Box::new(s)
}
// 根据之前学的,dyn Trait展开如下
// fn f4<'a>(s:&'s str)->Box<dyn Foo + 'static>
// 运行报错
fn f4(s:&str) -> Box<dyn Foo>{
Box::new(s)
}
// 修复手动标注:fn f4<'a>(s:&'a str) -> Box<dyn Foo + 'a>
总结:dyn trait是不会捕获参数的生命周期的,根据上一节我们学的,它有自己的规则。而impl trait作为返回值时,是会捕获参数类型和生命周期的。
九 HRTB和GAT
- 什么是HRTB 主要是针对fn类型,文档示例中看到只有fn、Fn、FnMut,没有见FnOnce.如何定义呢?看文档也知道怎么定义,它的语法是这样的,更详细的可以看文档:RFC0387
rust
for<lifetimes> Trait<T1, ..., Tn>
举出示例
rust
fn main(){}
// 此语法可用于 where 子句和类型
// 会报错,因为 zero变量的生命周期没有'a那么长
fn call_on_ref_zero<'a,F>(f:F)
where
F:Fn(&'a i32)
{
let zero = 0;
f(&zero);
}
// 修改 for<'a> 任意一个'a
// 在这里就指的是zero引用的生命周期
fn call_on_ref_zero<F>(f:F)
where
F:for <'a> Fn(&'a i32)
{
let zero = 0;
f(&zero);
}
// 也可以隐式的 Fn本身就是高阶的
fn call_on_ref_zero<F>(f:F)
where
F:Fn(&i32)
{
let zero = 0;
f(&zero);
}
- 早绑定和晚绑定
核心点就是下面这段impl 的 self 类型和与 impl 关联的 where 子句上有引用的话是早绑定,其他是晚绑定
摘抄RFC0387: The rule is that any lifetime parameter 'x
declared on an impl is considered early bound if 'x
appears in any of the following locations:
- the self type of the impl;
- a where clause associated with the impl (here we assume that all bounds on impl parameters are desugared into where clauses).
All other lifetimes are considered late bound.
When we decide what kind of trait-reference is provided by an impl, late bound lifetimes are moved into a for
clause attached to the reference. Here are some examples:
rust
// Here 'late does not appear in any where clause nor in the self type,
// and hence it is late-bound. Thus this impl is considered to provide:
//
// SomeType : for<'late> FnMut<(&'late Foo,),()>
impl<'late> FnMut(&'late Foo) -> Bar for SomeType { ... }
// Here 'early appears in the self type and hence it is early bound.
// This impl thus provides:
//
// SomeOtherType<'early> : FnMut<(&'early Foo,),()>
impl<'early> FnMut(&'early Foo) -> Bar for SomeOtherType<'early> { ... }
This means that if there were a consumer that required a type which implemented FnMut(&Foo)
, only SomeType
could be used, not SomeOtherType
:
rust
fn foo<T>(t: T) where T : FnMut(&Foo) { ... }
foo::<SomeType>(...) // ok
foo::<SomeOtherType<'static>>(...) // not ok
示例:这里是有点疑惑的,因为文档里写的是impl trait上才有早绑定和晚绑定,但示例的是普通函数
rust
// early bound 还没有被调用的时候
// late bound 调用的时候再确定
fn f<'a>(){} //late bound
fn g<'a:'a>(){} //early bound
fn main(){
// let ff = f::<'static> as fn(); // 是late bound就不能提前就行绑定
let ff = f as fn();
let gg = g::<'static> as fn();
}
- GAT通用的关联类型
说实话我有点没太理解,等以后有了实战经验了,再来阅读