< 返回版块

leolee0101 发表于 2022-08-15 15:38

Tags:lifetime

只有phantom 的结构,携带一个虚拟的引用,这个引用的lifetime是多少。
struct InvariantLifetime<'id>(PhantomData<*mut &'id ()>);

测试用例

上面链接中,几个例子中 InvariantLifetime 的lifetime 应该怎么推断?

====== 新增:
似乎像如下使用,InvariantLifetime也像普通引用一样,lifetime 至少到 最后一次使用。
但是被传参到函数内,似乎不是?

use std::marker::PhantomData;
//#[derive(Clone, Copy, Default)]
struct InvariantLifetime<'id>(PhantomData<*mut &'id ()>);

fn main() {

let t :InvariantLifetime = InvariantLifetime(PhantomData);

 fn strictly_match<'a>(_: &mut &'a String, _: &InvariantLifetime<'a>)
{}

{

    let ss = String::from("asd");

    strictly_match(&mut &ss, &t);
    drop (ss);
}
//@ 和普通引用一样,如果这里使用,会报错:t的life超过了ss
t;
}

评论区

写评论
苦瓜小仔 2022-08-15 22:39

我知道我的哪句话误导你了。

ss 来自于函数内部,其引用只在函数内部有效,所以 InvariantLifetime<'_> 能一直存在,但 &ss 无法和它那样一直存在(因为 ss 离开函数体不存在,所以 &ss 不存在)。

嗯,我的表述这里不对。没有歧义的话是:

ss 来自于函数内部,其引用只在函数内部有效,但 InvariantLifetime<'1> 的 '1 始终比 &'2 ss 的 '2 更长,它们永远无法相等。

我指的“&ss 无法和它那样一直存在”,就是 '2 无法和 '1 那样存在时间(跨度)同样长。

至于 “因为 ss 离开函数体不存在,所以 &ss 不存在”,这句话是对的,但 InvariantLifetime 同样无法离开函数体。

写的时候我可能想成了 fn f(phantom: &InvariantLifetime<'_>)

苦瓜小仔 2022-08-15 22:25

Note that lifetimes only restrict the end of life of things.

这句话也没错。因为生命周期关注的是,在哪里结束,从而不会出现引用失效。但这句话不是关键。


fn f<'a>(s: &'a str, cell: &Cell<&'a str>) {}
fn g(c: Cell<&'1 str>) { // 请保持 '1 不变,因为它是 invariant
    let string = String::from("outer: string");
    f(&string, &c); // f(&'2 str, &'tmp Cell<&'1 str>)
}

'1 与 '2 相等只有两种途径:

  1. f(&'2 str, &'tmp Cell<&'2 str>):这不行,因为 invariance, '1 必须保持不变,无法进行任何变化
  2. f(&'1 str, &'tmp Cell<&'1 str>):也不行,由于 '1 大于 '2,无法把 '2 变得更长, &'2 str 要么保持 '2,要么通过 & 的协变,变成 '3(当然 '2: '3),即一个更短的生命周期
作者 leolee0101 2022-08-15 22:04

这个我马上看下。但您觉得他说的"Note that lifetimes only restrict the end of life of things. "是什么意思,而且看了回答,从头到尾都是说的 life 到哪里end ,从没说从哪儿 start 。

👇
苦瓜小仔: 我知道,那是我提问的。

// 那个例子是
fn f<'a>(s: &'a str, cell: &Cell<&'a str>) {}
// 抽象一下: fn(&'a ..., &Invariant<'a>)

// 这里的例子是
fn strictly_match<'a>(_: &mut &'a String, _: &InvariantLifetime<'a>)  {}
// 抽象一下:fn(&mut &'a ..., &Invariant<'a>

嗯,从型变看,的确没什么太大的区别。

但你这次问的,类似于

fn g(c: Cell<&str>) {
    let string = String::from("outer: string");
    f(&string, &c);
}

g 无法编译。

苦瓜小仔 2022-08-15 21:53

我知道,那是我提问的。

// 那个例子是
fn f<'a>(s: &'a str, cell: &Cell<&'a str>) {}
// 抽象一下: fn(&'a ..., &Invariant<'a>)

// 这里的例子是
fn strictly_match<'a>(_: &mut &'a String, _: &InvariantLifetime<'a>)  {}
// 抽象一下:fn(&mut &'a ..., &Invariant<'a>

嗯,从型变看,的确没什么太大的区别。

但你这次问的,类似于

fn g(c: Cell<&str>) {
    let string = String::from("outer: string");
    f(&string, &c);
}

g 无法编译。

作者 leolee0101 2022-08-15 21:38

在上次有个问题里,您帮我在官方论坛里发起的提问中。有大佬回答的"Note that lifetimes only restrict the end of life of things. "
https://users.rust-lang.org/t/help-me-to-understand-invariance-and-lifetime-here/77835 .

--
👇
苦瓜小仔: > lifetime 约束是否只是看life end 的地方?

不是。如果只看 lifetime 实际在哪结束,为什么需要函数体的生命周期标注呢?

'tmp2 和 'outer 都是止步于 strictly_match 调用之后。

是的。但你依然没看到 'tmp2 无法与 'outer 相等:'outer 永远大于 'tmp2。

苦瓜小仔 2022-08-15 21:34

lifetime 约束是否只是看life end 的地方?

不是。如果只看 lifetime 实际在哪结束,为什么需要函数体的生命周期标注呢?

'tmp2 和 'outer 都是止步于 strictly_match 调用之后。

是的。但你依然没看到 'tmp2 无法与 'outer 相等:'outer 永远大于 'tmp2。

作者 leolee0101 2022-08-15 21:30

我也迷糊了。先达成一个观点:lifetime 约束是否只是看life end 的地方?如果是,'tmp2 和 'outer 都是止步于 strictly_match 调用之后。

--
👇
苦瓜小仔: ```rust fn test_phantom_life<'outer>(phantom: InvariantLifetime<'outer>) { let ss = String::from("asd"); let ref_phantom = &phantom; // &'tmp1 InvariantLifetime<'outer> let ref_ss = &ss; // &'tmp2 String strictly_match(&mut ref_ss, ref_phantom); // 'tmp2 无法等于 'outer // NLL: phantom 和 ss 的生命周期的确止步于此,但这不是关键所在 }


苦瓜小仔 2022-08-15 21:29

InvariantLifetime<'outer> 在函数内部不会改变 'outer,因为这就是 invariance。

苦瓜小仔 2022-08-15 21:24
fn test_phantom_life<'outer>(phantom: InvariantLifetime<'outer>) {
    let ss = String::from("asd");
    let ref_phantom = &phantom; // &'tmp1 InvariantLifetime<'outer>
    let ref_ss = &ss; // &'tmp2 String 
    strictly_match(&mut ref_ss, ref_phantom); // 'tmp2 无法等于 'outer
    // NLL: phantom 和 ss 的生命周期的确止步于此,但这不是关键所在
}
作者 leolee0101 2022-08-15 21:16

lifetime约束是不是只看life end 的位置?进入函数之前 InvariantLifetime<'_> 的life end 是未知的,到了最后使用的地方strictly_match(&mut &ss, &phantom) &ss 和 sInvariantLifetime<'_> 都是最后一次使用,life end 不是一样吗

--
👇
苦瓜小仔: 'inner 和 'outer 一样在协变情况下是可以发生。

fn test_phantom_life<'outer>(phantom: CovariantLifetime<'outer>) {
    let ss = String::from("asd");
    // CovariantLifetime<'outer> -> CovariantLifetime<'inner>
    strictly_match(&mut &ss, &phantom); // strictly_match(&mut &'inner String, &CovariantLifetime<'inner>);
}

但你这里是不变啊。

NLL 的确让变量的生命周期终止于最后使用的时候,但 strictly_match(&mut &ss, &phantom); 这行显然还在使用中,所以哪里有问题?

苦瓜小仔 2022-08-15 21:11

'inner 和 'outer 一样在协变情况下是可以发生。

fn test_phantom_life<'outer>(phantom: CovariantLifetime<'outer>) {
    let ss = String::from("asd");
    // CovariantLifetime<'outer> -> CovariantLifetime<'inner>
    strictly_match(&mut &ss, &phantom); // strictly_match(&mut &'inner String, &CovariantLifetime<'inner>);
}

但你这里是不变啊。

NLL 的确让变量的生命周期终止于最后使用的时候,但 strictly_match(&mut &ss, &phantom); 这行显然还在使用中,所以哪里有问题?

作者 leolee0101 2022-08-15 21:08

lifetime 约束不是只约束结束的位置吗?"Note that lifetimes only restrict the end of life of things. "https://users.rust-lang.org/t/help-me-to-understand-invariance-and-lifetime-here/77835 .

--
👇
苦瓜小仔:

fn test_phantom_life<'outer>(phantom: InvariantLifetime<'outer>) {
    let ss = String::from("asd");
    strictly_match(&mut &ss, &phantom); // strictly_match(&mut &'inner String, &InvariantLifetime<'outer>);
}

在你标注的这种不变的情况下,'inner 怎么可能和 'outer 一样?

--
👇
leolee0101: InvariantLifetime<'_>,在传入函数后,除了strictly_match(&mut &ss, &phantom),之后ss 和 InvariantLifetime<'_> 都不再使用。 test 函数返回之后,两者也不会使用。根据NLL,他们的life 不是只到最后一次使用的地方吗?

苦瓜小仔 2022-08-15 20:58
fn test_phantom_life<'outer>(phantom: InvariantLifetime<'outer>) {
    let ss = String::from("asd");
    strictly_match(&mut &ss, &phantom); // strictly_match(&mut &'inner String, &InvariantLifetime<'outer>);
}

在你标注的这种不变的情况下,'inner 怎么可能和 'outer 一样?

--
👇
leolee0101: InvariantLifetime<'_>,在传入函数后,除了strictly_match(&mut &ss, &phantom),之后ss 和 InvariantLifetime<'_> 都不再使用。 test 函数返回之后,两者也不会使用。根据NLL,他们的life 不是只到最后一次使用的地方吗?

作者 leolee0101 2022-08-15 20:52

@ 苦瓜小仔 兄,“ss 来自于函数内部,其引用只在函数内部有效,所以 InvariantLifetime<'_> 能一直存在,但 &ss 无法和它那样一直存在”。InvariantLifetime<'_>,在传入函数后,除了strictly_match(&mut &ss, &phantom),之后ss 和 InvariantLifetime<'_> 都不再使用。 test 函数返回之后,两者也不会使用。根据NLL,他们的life 不是只到最后一次使用的地方吗?

苦瓜小仔 2022-08-15 20:36

先回答为什么不能编译

这需要关注函数签名中生命周期标注的含义。

fn strictly_match<'a>(_: &mut &'a String, _: &InvariantLifetime<'a>) 这个函数所表明的生命周期“契约”之一是:

  1. 只要 &'a String 一直存在,InvariantLifetime<'a> 就必须一直存在;
  2. 只要 InvariantLifetime<'a> 一直存在, &'a String 就必须一直存在。

好了,这句话可以解释除这个函数之外,你列举的函数为什么无法编译。

fn test_phantom_life(phantom: InvariantLifetime<'_>) {
    let ss = String::from("asd");
    strictly_match(&mut &ss, &phantom);
}

ss 来自于函数内部,其引用只在函数内部有效,所以 InvariantLifetime<'_> 能一直存在,但 &ss 无法和它那样一直存在(因为 ss 离开函数体不存在,所以 &ss 不存在)。

同理:

fn test_phantom_life1(ss: String, phantom: InvariantLifetime<'_>) {
    strictly_match(&mut &ss, &phantom);
}

虽然 ss 来自外部,但其引用产生于函数内部,所以无法编译。同理,另外两个只是参数使用的模式不一样之外,与这个函数并无区别。

再思考为什么能编译的部分

let t: InvariantLifetime = InvariantLifetime(PhantomData);
let ss = String::from("asd");
strictly_match(&mut &ss, &t);

把关注点放到 InvariantLifetime 上,一个最基础的问题,变量 t 是什么类型?

如果你试图去理解生命周期,那么这是必须清楚的,而不是只看到你所标注的 InvariantLifetime,却遗忘它的生命周期参数。

它是一个被推断的生命周期,其类型为 InvariantLifetime<'_>,而且不是 InvariantLifetime<'static>

给这个推断的生命周期一个名称 'pass,那么 t 的类型是 InvariantLifetime<'pass>

只要能“证明”确实可以有 &'pass ssInvariantLifetime<'pass>,那么编译通过就不奇怪,对吧?

PhantomData 确实是一个非常复杂的东西,当你把它具体化

struct PhantomData<'p>(*mut &'p ());
struct InvariantLifetime<'id>(PhantomData<'id>);

fn main() {
    {
        let phantom: PhantomData<'_> = PhantomData(&mut &() as _);
        let t: InvariantLifetime = InvariantLifetime(phantom);
        let ss = String::from("asd");
        strictly_match(&mut &ss, &t);
    }

}
fn strictly_match<'a>(_: &mut &'a String, _: &InvariantLifetime<'a>) {}

一切都可以讲清楚了:

let phantom: PhantomData<'_> = PhantomData(&mut &() as _); 其实与如下内容等价

let unit = ();
let ref_unit = &unit; // &'tmp1 unit -> &'pass unit (协变)
let phantom_inner: *mut &'_ () = &mut &() as _; // *mut &'pass () 不变
let phantom: PhantomData<'_> = PhantomData(phantom_inner);
let t: InvariantLifetime = InvariantLifetime(phantom);

&unit 对生命周期是协变的,在 let phantom_inner: *mut &'_ () = &mut &() as _; 之前可以缩短成 let phantom_inner: *mut &'pass () = &mut &() as _;

同理, &ss 对生命周期是也是协变的,strictly_match(&mut &ss, &t); 等价于:

let ref_ss = &ss; // &'tmp2 ss -> &'pass ss (协变)
let mut_ref_ss = &mut &ss; // &mut &'pass ss 不变
strictly_match(mut_ref_ss, &t);

&'pass ssInvariantLifetime<'pass> 都存在,所以代码通过!

这种标注的问题

fn strictly_match<'a>(_: &mut &'a String, _: &InvariantLifetime<'a>) 这种标注当然是完全不推荐,它相当无用。

这至少会带来一个问题:当引用 &'a String 不再存在时InvariantLifetime<'a> 不能再使用(如果使用,就是违反生命周期契约,导致编译失败):

{
...
let ss = String::from("asd");
strictly_match(&mut &ss, &t);
drop(ss); // ss 不能先于 t drop
drop(t);
}

{
...
let mut ss = String::from("asd");
strictly_match(&mut &ss, &t);
ss.push('a');
t; // 无法使用 t
}

解决办法

fn strictly_match(_: &mut &String, _: &InvariantLifetime)

这相当简洁,而且生命周期约束很宽松:&StringInvariantLifetime<'_> 没有任何关系,各自的生命周期互不影响,所以也就没有我提到的问题

1 2 共 35 条评论, 2 页