rust 生命周期
1 先看个例子
--> src/main.rs:6:13
|
5 | let x = 5;
| - binding `x` declared here
6 | r = &x;
| ^^ borrowed value does not live long enough
7 | }
| - `x` dropped here while still borrowed
8 |
9 | println!("r: {}", r);
| - borrow later used hererust编译器 如何发现问题的呢?
2 借用检查器
The Borrow Checker
Caution
借用的生命周期必须比 所有者的生命周期 短, 就是 所有者离开作用域前,你才可以使用它的借用
rust编译器 有一个 borrow checker 它会比较作用域来判断所有借用是否合法
标注上变量的生命周期,我们看看
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+这里用 ’a 表示 变量r 的生命周期, ’b 表示变量x 的生命周期 变量r (借用了x)的生命周期 ’a 比 所有者x 的生命周期 ’b 长 , 而这个时候x的生命周期 ’b 已经结束了,x不可用了
将上面的代码修改成如下,就ok了
3 生命周期的标注
3.1 为什么需要
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = "xyz".to_string();
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
分析一下上面例子
longest返回一个借用, 那么我们必须知道, 这个返回值(调用方的接收者)借用的值的生命周期要比这个借用长,编译器需要能够检查是否达到这样的要求- 因为函数内的局部变量在函数执行完毕后, 都会失效的, 所以返回值是借用的情况下, 它肯定只能是来至于函数参数且该参数必定是引用类型 (这里 暂不考虑 &’static ),只要知道它来至于哪个, 就能确定接收者和谁的生命周期有关
- 现在所有问题的关键在于编译器不知道longest函数返回值来至于哪个参数,而生命周期标注最终就是为了让编译器知道它返回的引用与谁有关,这样就知道最后需要和谁的生命周期比较, 这样就帮助了编译器进行静态分析,去防止悬垂指针和其他内存安全问题
- 生命周期标注 只是描述多个借用之间的关系, 并不会改变他们的生命周期
- 上面的例子中编译器不知道返回哪个, 意味着编译器需要将2种情况都考虑,使用情况最坏的哪个,就是生命周期最短的哪个, 因为你不可能让编译器强制认为返回其中一个,让编译通过吧? 例子中 最坏的情况是返回 string2的借用, 因为string2 生命周期很短,会导致result 悬垂引用
3.2 标注的真实含义
Note
- 只有引用类型才需要标注
- 这个就类似类型来约束你申请的内存, 生命周期标注是表示你的生命周期在哪个范围 , 可以理解为: 生命周期标注表示生命周期的类型
3.3 函数
前置知识: 什么是函数的签名(Function Signature)
在编程中, 是指函数的定义中描述函数接口的部分. 它通常包括以下几个方面:
- 函数名: 函数的名称,用于标识和调用函数
- 参数列表: 函数接受的参数及其类型. 参数列表定义了函数可以接收的数据类型和数量
- 返回类型: 函数返回值的类型.如果函数不返回值,通常会标记为 void(在 C/C++ 中)或省略返回类型(在 Rust 中为 ())
- 生命周期(在 Rust 中): Rust 中的函数签名还包括生命周期注解,用于描述引用之间的生命周期关系
使用 ’a ’b 这种 单个简单字母, 放在 &后面
也可以先去看 covariant-协变, 早期还没看covariant相关教程,只是看完book那个教程, 浪费了很多思考, 教程分不同批次,对于我来说是恶心的.
报错了
分析
- 上面这个例子, 你可能最开始有这样的疑惑,这不是很明显返回的是x吗,还需要标注生命周期吗?
- 提示错误说函数签名没有标注生命周期
- 高亮第8行处, 实际上编译器在编译到这里的时候,它是只能根据函数签名的信息来判断返回值来至于哪里,所以它不知道该函数返回值和谁有关了., 我们继续看例子2
还是报错了
分析
- 好了,这样我们标注了 返回值和y有关, 唉? 这是不是感觉就行了啊?
- 报错了, 因为编译器编译longest函数, 它确实肯定是知道返回值是x的, 然而我们设置的返回值类型是
&'a str, x能否转换为这个类型?- 不行!, 因为x的标注是
'b(没写就是默认自己的生命周期,这里就用'b表示) 与'a没有指明任何关系,&'b str类型 不能转换为&'a str
- 不行!, 因为x的标注是
- 把’a 生命周期标注作为类型的一部分
// 从代码上看 y和返回值 没有任何关系, 那么我们不需要标注
// 表示 返回值的生命周期必须符合 'a这样长的范围
// 就这个例子而言, 编译器编译这个函数时是知道它返回 了 x
// 而x 符合 返回类型 &'a str , 本身就是 'a 这样的长度
// 'a 的长度或说范围 是实际调用该函数时传递的参数的生命周期, 实际调用处是string1 是main函数都有效的生命周期
// 然后实际调用处接收者 result的生命周期 比如说是 'c , 将 'a 类型的 赋值给 'c 类型的 能否成功, 这里是可以的, 他们生命周期长度一样.
// 赋值相当于类型转换, 'a 类型 转换为 'c 类型, 'a 如果是 'c 的子类 就可以了, 表示 'a的生命周期 >= 'c
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
// 注意编译器编译到这里, 它是不关注函数体的, 它只会关注到函数的签名
// 如果函数签名没有生命周期标注,它根本不知道返回值和哪个参数有关.
// 现在发现有了生命周期标注, 表示返回值和string1有关系
// 那么 string2的死活与它无关了.
result = longest(string1.as_str(), string2.as_str());
} // 这个时候 string2 指向的 xyz (在堆上) 已经被free了
// 而编译器知道 longest 返回的是来至于第一个参数x: string1.as_str()
// 这个所谓的知道是通过 生命周期标注 知道的
// ok
println!("The longest string is {}", result);
}
Caution
例子2里我们说 ’b 因为没有指明 和’a的关系, 所以他们之间不能转换, 那么他们需要什么样的关系才能转换能?
// 设置返回值生命周期 约束为 'a , 表示至少和y 一样长
// 但是实际返回值是x, 它的生命周期 是 'b
// 'b 能不能转为 'a 就是问题的关键了. 当'b 是 'a的子类就可以了,('b:'a) 表示'b 生命周期比 >= 'a
// 子类, 就是继承了父类 所有特征的类, 所以它可以转换为 父类, 这个好理解的(这里是子类生命周期比父类长)
fn longest<'a,'b:'a>(x: &'b str, y: &'a str) -> &'a str {
x
}
fn longest2<'a, 'b>(x: &'b str, y: &'a str) -> &'a str
where // 和泛型 使用方式类似, 可以这样写
'b: 'a,
{
x
}
fn main(){
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
// string1 的生命周期比 string2 长
// 为了符合 签名中 'b 要>= 'a , 只能转换为 'b = string2的生命周期
// 这样 'b = 'a 符合 'b:'a
// 所以实际返回的x 它的生命周期变成了 string2 一样了,
// 当然根据签名我们直接知道与string2有关,
// 它的生命周期就是string2的生命周期,因为string2 能活多久,它就多久
// 编译直接报错,提示string2, borrowed value does not live long enough
// 从代码角度看, 返回的值的生命周期和 string2一样, 而result的生命周期 比这个长,
// 无法从一个 短的生命周期 赋值给一个长的生命周期, 可理解为父类无法转换为子类
result = longest(string1.as_str(), string2.as_str());
} // string2 释放
println!("The longest string is {}", result);
}报错了
// 生命周期标注表示返回值与x,y有关, 表示 返回值的生命周期必须符合 'a这样长的范围
// 返回值 x 符合 'a 类型的生命周期 编译通过
// 传递来的时候, 确定出 'a =x的实参的生命周期 , 然后又确定出 'a=y的实参的生命周期
// 最终是 'a = x和y 实参生命周期短的那个
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
x
}
fn main(){
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
// 注意编译器编译到这里, 它是不关心函数体的, 它只会关注到函数的签名
// 所以它根本不知道返回值和哪个参数有关.(虽然函数内部很明显返回了谁)
// 现在看了函数签名, 发现有了生命周期标注, 表示string1,string2有关系
// 从人的角度看, 可以理解为, 编译器在这种情形下会考虑最坏的情况,
// 所以这里认为返回值 返回生命周期短的那个, 就是string2,所以最终和string2有关
// 从代码角度看, 返回的值的生命周期是 string2, 而result的生命周期 比这个长,
// 无法从一个 短的生命周期 赋值给一个长的生命周期, 可理解为父类无法转换为子类.
result = longest(string1.as_str(), string2.as_str());
} // 这个时候 string2 指向的 xyz (在堆上) 已经被free了
// 报错了
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
}
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = "xyz";
result = longest(string1.as_str(), string2);
} // string2 所借用的 xyz 这个数据实际是在 只读区中分配的,生命周期是到程序运行结束,所以到这里,没有被free掉
// println!("{}", string2); 注意这个还是会报错的, 因为变量string2 离开作用域 已经不可用了
// OK
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
}
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
生命周期的总结
- 返回值的 ’a 标注 如果与多个函数参数有关, 那么选择 生命周期短的那个参数, 表示 返回值与 该参数有关, 最后借用检查就知道返回值的生命周期和谁做比较
- 如果返回值 ’a 就和一个参数有关, 那么就和它有关
3.4 结构体
Important
- 生命周期标注 ’a 表明这两个字段的生命周期是相同的,并且它们至少活得和 Book 实例一样久
- 如果没有生命周期标注,编译器就无法确定title 和 author 引用的数据是否会在 Book 实例化之前被释放
- 生命周期标注提供了一种方式,让编译器能够检查引用是否在有效范围内,从而确保内存安全
3.5 约束
Important
- T: ’a
- 在 T 中的 所有引用都必须至少和生命周期 ’a 活得一样长 (>=’a)
- T: Trait + ’a
- T 类型必须实现 Trait trait, 并且在 T 中的所有引用都必须至少和 ’a 活得一样长
- &’a T
- ’a 表示T 的生命周期至少和’a 一样长
fn main() {
let s2 = "hello".to_string();
test2(&s2); // ok
test2(&&s2); // 报错了
test1(&s2) // 报错了 &'static T , T 需要满足'static
}
fn test1<T>(_arg: &'static T) {
println!("hello");
}
// T: 'static 表示T中使用的引用 必须至少 比'static 一样长
// test2(&s2); 调用的情况 这里T 是 String 类型, 不是引用类型,所以啥都行
fn test2<T: 'static>(_arg: &T) {
println!("hello");
}3.6 &’static
Tip
整个程序的持续时间就是静态生命周期
3.7 泛型,trait
Important
lifetimes are a type of generic,所以会将它和泛型T的申明放在一起
3.8 标注省略三规则
// 早期像这样的,都是需要 进行生命周期标注的
// fn first_word(s: &str) -> &str {
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的开发人员注意到, 每次都要写很多这种标注, 很麻烦, 于是总结了一些规则,让编译器自动处理
- 概念说明
- 生命周期在函数的参数时,被称为输入生命周期
- 生命周期在函数的返回值时,被称为输出生命周期
- 三个规则
- 每个引用参数都有自己的生命周期参数
- 如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
- 如果有多个输入生命周期参数, 且其中一个是&self 或 &mut self(是方法), 则self的生命周期会被赋予所有的输出生命周期参数
- 如果编译器应用完这3个规则,还有无法确定生命周期,那么就报错
// 我们写的代码
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 {}
// 我们写的代码
fn longest(x: &str, y: &str) -> &str {}
// 1. 编译器应用第一条规则后
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
// 2. 编译器应用第二条规则,发现有多个输入参数,pass
// 3. 编译器应用第三条规则,发现是个函数,不是方法,pass
// 结果, 返回值的生命周期 还是不确定, 所以报错了结构体方法生命周期
// 需要在 impl后面申明,这个生命周期是结构体类型的一部分
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
// 1. 编译器应用第一条规则, &'a self, 所以我们不用标注了
// 我们来看看方法中多个参数的情况,
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
// 1. 编译器应用第一条规则后,
fn announce_and_return_part(&'a self, announcement: &'a str) -> &str {}
// 2. 编译器应用第三条规则后,
fn announce_and_return_part<'a>(&'a self, announcement: &'a str) -> &'a str {}4 drop check
5 subtyping1
Important
- 在前面章节中 我们看到下面这样的代码
- 这里 其实明确表示 需要传递的参数的 生命周期是一样长的,然而在实际调用的时候, 传递的参数的生命周期不一样,为什么可以这样呢
- 还有返回值的生命周期类型 和接收者的生命周期类型 也是不一样的, 为什么也可以成功赋值呢
- 之前就说到子类可以转换为 父类,rust 提供这样的机制,可以让子类生命周期(长生命周期)降级转换为父类生命周期(短生命周期), 所以编译不会报错
// x 生命周期长的情况, 就降级转换为 和 y一样的短的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
x
}
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}6 Variance 型变2
| Type | Variance in ’a | Variance in T |
|---|---|---|
&'a T |
covariant | covariant |
&'a mut T |
covariant | invariant |
Box<T> |
covariant | |
Vec<T> |
covariant | |
*const T |
covariant | |
*mut T |
invariant | |
[T] and [T; n] |
covariant | |
fn() -> T |
covariant | |
fn(T) -> () |
contravariant | |
std::cell::UnsafeCell<T> |
invariant | |
std::cell::Cell<T> |
invariant | |
dyn Trait<T> + 'a |
covariant | invariant |
6.1 covariant 协变
Tip
- 协变指的是在类型层次中, 子类型可以替代父类型
- 当
T:U(T是U的子类) 时,F<T>如果也是F<U>的子类,那么就称为F<T>对于参数T是协变关系 - 在rust中, 生命周期有这类机制
协变规则
&'a T对于'a是协变的, 当'a:'b,那么&'a T也是&'b T的子类
例子说明
// 要求 2个借用变量的生命周期一样长, 而实际上传递的参数的生命周期不一样
// rust 会通过将长的生命周期的参数 降级为 短生命周期参数一样长的 生命周期
fn debug<'a>(a: &'a str, b: &'a str) {
println!("a = {a:?} b = {b:?}");
}
fn main() {
let hello: &'static str = "hello";
{
let world = String::from("world");
// 'world 生命周期短于 'static
let world = &world;
// hello 隐式的从 &'static str 降级 或者说转换为 &'world str
// &'static str是 &'world str 的子类, 所以可以执行成功
debug(hello, world);
}
}
协变规则
&'a T对于T是协变的, 当T:U,那么&'a T也是&'a U的子类
Important
- 下面例子中的test1, 我不知道网上是否有人也这样测过, 一开始根据参数的推理,我是很快就想到的,不过返回值的推理没有马上注意到,可以看看下面的注释.
// 根据前面结构体章节, 我们知道 该结构体对于T 和 'a 都是协变的
struct MyStruct<'a, T: 'a> {
ptr: &'a T,
}
// 编译通过, 可能没想到 这个竟然能通过
// 看参数 我们知道T:'a 表示T 中引用必须至少和'a 一样长,
// 传递的T = &'b str, 这个时候T的生命周期是'b,
// 它必须活着至少>= 'a ,则MyStruct<'a, &'b str> 推断出 'b:'a
// 看返回值 MyStruct<'b, &'a str>,T的生命周期是'a, 它至少和'b 一样长, 即推断出: 'a:'b
// 现在'a:'b 'b:'a , 其实 'a='b
fn test1<'a, 'b>(v: MyStruct<'a, &'b str>) -> MyStruct<'b, &'a str> {
v
}
// 看看下面4个测试 来间接说明我们test1 的判断
// 这个符合我们的判断对于 参数'a 和T 都是协变的, 很容易理解的版本
fn test2<'a: 'c, 'b: 'd, 'c, 'd>(v: MyStruct<'a, &'b str>) -> MyStruct<'c, &'d str> {
v
}
// 很容易得出 我们需要 'b:'c 才能通过编译
fn test3<'a, 'b: 'c, 'c>(v: MyStruct<'a, &'b str>) -> MyStruct<'a, &'c str> {
v
}
// 现在我们看看这个,'b:'c 才能通过编译, 而这里我们用 'a:'c 也能通过, 间接说明 'b:'a
fn test4<'a: 'c, 'b, 'c>(v: MyStruct<'a, &'b str>) -> MyStruct<'a, &'c str> {
v
}
// 通过编译的.
// 参数推断出 'a:'b , 返回值推断出 'b:'c
// 刚好符合 我们的要求, 直接不用设置了
fn test5<'a, 'b, 'c>(v: MyStruct<'b, &'a str>) -> MyStruct<'c, &'b str> {
v
}
fn main(){}6.2 Contravariance 逆变
Tip
逆变指的是在类型层次中, 父类型可以替代子类型
6.3 invariant 不变
Important
不是协变也不是逆变的情况, 就是不变
不变规则
&'a mut T对于T是不变的, 这意味着你不能将&'a mut T隐式地转换成&'a mut U即使T是U的子类型或者父类型- 和 协变规则2 比较一下
例子说明
// 前面已经验证 &'b str 是&'a str 的子类
// T 就是 &'b str ,U 是 &'a str
// 然而编译报错, 说明了&'a mut &'b str 不是 &'a mut &'a str 的子类
// vscode 提示的错误 ,可以了解到 &'a mut T 对于T 是不变的
fn covariant_ref<'a, 'b: 'a>(x: &'a mut &'b str) -> &'a mut &'a str {
x
}
fn main() {
let mut x: &str = "hello";
let y: &&str = covariant_ref(&mut x);
println!("{}", y);
}6.4 自定义类型的情况
看下面代码
- 如果所有使用泛型A 的成员 对A 都是 协变的, 那么 MyType 对于A 就是 协变的
- 如果所有使用泛型A 的成员 对A 都是 逆变的, 那么 MyType 对于A 就是 逆变的
- 否则就是不变的
use std::cell::Cell;
struct MyType<'a, 'b, A: 'a, B: 'b, C, D, E, F, G, H, In, Out, Mixed> {
a: &'a A, // covariant over 'a and A
b: &'b mut B, // covariant over 'b and invariant over B
c: *const C, // covariant over C
d: *mut D, // invariant over D
e: E, // covariant over E
f: Vec<F>, // covariant over F
g: Cell<G>, // invariant over G
h1: H, // would also be covariant over H except...
h2: Cell<H>, // invariant over H, because invariance wins all conflicts
i: fn(In) -> Out, // contravariant over In, covariant over Out
k1: fn(Mixed) -> usize, // would be contravariant over Mixed except..
k2: Mixed, // invariant over Mixed, because invariance wins all conflicts
}7 PhantomData 类型3
phantom: 幽灵, 无形的东西, 假象,虚构的事物 PhantomData 翻译为 幽灵数据 好了…
7.1 为什么需要4
struct Iter<'a, T: 'a> {// 没有使用到生命周期参数 'a
ptr: *const T,
end: *const T,
}
struct Dog<T> { // 没有使用到类型参数 T
data: i32
}
分析
- 在自定义类型的情况中,我们提到自定义类型对泛型参数A(生命周期也是一种泛型参数)的型变关系需要通过成员对A的型变关系来推断, 但是上面代码
struct Iter<'a,T:'a>申明了生命周期参数, 但是实际光有申明又没法使用它(裸指针本身不带生命周期参数), 这样导致无法知道该类型的型变关系, 因此编译器会报错 struct Dog<T>申明了泛型T但是实际里面没有使用它,Dog<i32>和Dog<i64>就没有什么区别, 那么编译器会推断他们是可以互换的,这就很不合理- PhantomData 类型可以解决这些问题,
std::marker::PhantomData是一个特殊的标记类型, 不占用任何内存空间 ZST - 作用
- 向编译器提供对静态分析有用的信息, 比如正确处理 泛型参数(T和生命周期)的 型变
- 它用于在类型系统中表示和某类型有关, 但实际上并不存储该类型的值, 可以消除未使用类型参数的警告
- 控制 Variance (协变,逆变还是不变)
7.2 消除未使用类型参数的错误
7.3 控制Variance
7.3.1 控制 T
| Type | Variance in T |
|---|---|
PhantomData<T> |
covariant |
PhantomData<fn(T)> |
contravariant |
PhantomData<Cell<T>> |
invariant |
Important
- 使整个结构体对于 T 是协变的
Important
- 使整个结构体对于 T 是逆变的
7.3.2 控制 ’a
| Type | Variance in ’a |
|---|---|
PhantomData<&'a ()> |
covariant |
PhantomData<fn(&'a ())> |
contravariant |
PhantomData<Cell<&'a ()>> |
invariant |
Important
- 使整个结构体对于 ’a 是协变的
PhantomData<&'a ()>
use std::marker::PhantomData;
struct MyIter<'a, T: 'a> {
ptr: *const T,
end: *const T,
// 我们知道结构体对于'a 的型变关系 取决于 里面的成员对于'a 的型变关系
// 本来结构体没有使用'a , 现在我们假装有一个成员 是 &'a (), 比如假装一个成员是 name: &'a ()
// &'a () 对'a 是协变的, 只要该成员使用了'a ,那么整个结构体对于'a 就是协变的了 (因为只有这个成员使用了'a)
// 而对于T 来说, 看ptr 和end 成员就知道了, 他们对T 是协变的, 所以结构体对于T 也是协变的了
// 可能有人会说 为什么不用 PhantomData<&'a T> , 因为结构体对于T 的型变规则已经决定了, 由成员ptr 和end 确定
// 所以你没有这个意图去写成 () , 等下后面的例子我们 改成 mut T 就能更理解了.
_marker: PhantomData<&'a ()>,
}
// 编译ok
fn test1<'a: 'b, 'b, T: 'a>(v: MyIter<'a, T>) -> MyIter<'b, T> {
v
}
// 编译ok了, T这个时候=&'a str 返回值的T 是&'c str
// 说明了 MyIter 对于T 是协变
fn test2<'a: 'c, 'b, 'c>(v: MyIter<'b, &'a str>) -> MyIter<'b, &'c str> {
v
}
fn main() {}PhantomData<&'a mut T>
use std::marker::PhantomData;
struct MyIter<'a, T: 'a> {
ptr: *const T,
end: *const T,
// 注意这里 T 实际是不可能这样写的, 因为关于结构体对T的协变规则编译器已经能得出了, 你是没有意图去这样做的
// 相当于假装这里有个成员 , 它的类型是 &'a mut T
// &mut T对T 是不可变的, 那么现在整个结构体对于T 从原来的协变 变成 不变了.
// 这改变了 原本对T 的型变情况(*const T 对T 是协变的), 是完全没有必要的, 没有这个意图的.
// 所以我们对这种情况使用 PhantomData<&'a ()> 就可以了.
_marker: PhantomData<&'a mut T>,
}
// 编译ok ,'a 是协变的
fn test1<'a: 'b, 'b, T: 'a>(v: MyIter<'a, T>) -> MyIter<'b, T> {
v
}
// 'a:'c , 'c:'a 说明需要'a='c, T 是不变的
fn test2<'a: 'c, 'b, 'c: 'a>(v: MyIter<'b, &'a str>) -> MyIter<'b, &'c str> {
v
}
// 编译ok
// 参数推断出'a:'b , 返回值推断出'b:'c
// 因为我们需要 'a='b , 设置 'b:'a 就能编译通过.
// 这里我们设置 'c:'a 能得出 'a='b='c了, 也是ok的
// 对于T 是不变的
fn test3<'a, 'b, 'c: 'a>(v: MyIter<'b, &'a str>) -> MyIter<'c, &'b str> {
v
}
fn main(){}PhantomData<&'a T>
use std::marker::PhantomData;
struct MyIter<'a, T: 'a> {
ptr: *mut T,
end: *mut T,
// 因为自定义类型 对T 的型变要看成员对T的型变
// 所以这里 PhantomData<&'a T>,设置上对T 是协变的
// 但是实际上 是 不变的, 因为 成员类型*mut T对T 是不变的
_marker: PhantomData<&'a T>,
}
// 编译ok
fn test<'a: 'b, 'b, T: 'a>(v: MyIter<'a, T>) -> MyIter<'b, T> {
v
}
// 编译错误, 因为MyIter 对于T 是不变的
fn test2<'a: 'b, 'b>(v: MyIter<'b, &'a str>) -> MyIter<'b, &'b str> {
v
}
fn main(){}
Important
- 使整个结构体对于 ’a 是逆变的