< 返回版块

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;
}

评论区

写评论
作者 leolee0101 2022-08-17 19:45

感觉是,"abc" 不像4 或()那样一般不需要分配存储空间,4可以嵌入cpu指令,()甚至不会出现在最后的指令或数据中。"abc"本身就代表一段内存,而且为了效率和重用,一般分配在静态存储区。
从而,如果对4或()引用,也就是取地址,就需要为他们分配一块内存,从而他们就和"abc"一样 可以全局存在。
而如果对 4或() 用let绑定到局部变量,他们就有了局部变量对应的堆栈地址。再对其引用,也就是取地址,就使用的局部变量的地址,而这块内存是随着函数退出,数据失效。所以这些引用life就不能是'static 。

--
👇
苦瓜小仔: 这个说法是不准确的。

"abc" 的类型是 &'static str,它可以存活任意长,直到程序结束。

() 并不是引用,它存活任意长是有条件的,所以 &() 不一定存活任意长

如果你仔细去看,会发现第一次我写的是:

let unit = ();
let ref_unit = &unit; // &'tmp1 unit -> &'pass unit (协变)

这次我写的是:

let ref_unit = &(); // &'static unit -> &'1 unit 协变

即没有声明 let unit = ();,&() 变成 &'static () 属于“静态提升”。

当然,我也可以都写成 let ref_unit = &(); // &'static () 这种再去分析:)

嗯,细节越来越多 :)

--
👇
leolee0101:

() 跟字符串字面值一样是 'static。

苦瓜小仔 2022-08-16 22:29

突然发现一个 typo:

let phantom_inner: *mut &'_ () = &mut &() as _;

应该是:

let phantom_inner: *mut &'_ () = &mut ref_unit as _;

:(

作者 leolee0101 2022-08-16 22:23

好的。
之前看 有些生成的 MIR ,就发现有 promoted 的数据结构,跟这儿连上了。

promoted[1] in main::{closure#0}: &&str = {
    let mut _0: &&str;                   // return place in scope 0 at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/macros.rs:317:55: 317:79
    let mut _1: &str;                    // in scope 0 at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/macros.rs:317:55: 317:79

    bb0: {
        _1 = const "\"ad\"";             // scope 0 at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/macros.rs:317:55: 317:79
                                         // mir::Constant
                                         // + span: /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/macros.rs:317:55: 317:79
                                         // + literal: Const { ty: &str, val: Value(Slice(..)) }
        _0 = &_1;                        // scope 0 at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/macros.rs:317:55: 317:79
        return;                          // scope 0 at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/macros.rs:317:55: 317:79
    }
}

--
👇
苦瓜小仔: 这个论坛也发了。

但两个都不是最新的。

最新的是我给的 github page 上的链接。

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

这个论坛也发了。

但两个都不是最新的。

最新的是我给的 github page 上的链接。

作者 leolee0101 2022-08-16 22:13

巧了,刚刚找到你在csdn 里面的那篇 -

--
👇
苦瓜小仔: 前段时间我整理过《临时引用的静态生命周期提升

苦瓜小仔 2022-08-16 22:08

前段时间我整理过《临时引用的静态生命周期提升

作者 leolee0101 2022-08-16 22:03

长学问了。等会找找 静态提升 的资料。

苦瓜小仔 2022-08-16 21:52

这个说法是不准确的。

"abc" 的类型是 &'static str,它可以存活任意长,直到程序结束。

() 并不是引用,它存活任意长是有条件的,所以 &() 不一定存活任意长

如果你仔细去看,会发现第一次我写的是:

let unit = ();
let ref_unit = &unit; // &'tmp1 unit -> &'pass unit (协变)

这次我写的是:

let ref_unit = &(); // &'static unit -> &'1 unit 协变

即没有声明 let unit = ();,&() 变成 &'static () 属于“静态提升”。

当然,我也可以都写成 let ref_unit = &(); // &'static () 这种再去分析:)

嗯,细节越来越多 :)

--
👇
leolee0101:

() 跟字符串字面值一样是 'static。

作者 leolee0101 2022-08-16 21:33

let ref_unit = &(); // &'static unit -> &'1 unit 协变

之前不知道,() 跟字符串字面值一样是 'static。

这个确实是看漏了,sorry,另外十分感谢。

苦瓜小仔 2022-08-16 21:22
fn test_phantom_life11(phantom: InvariantLifetime<'_>) { // InvariantLifetime<'1>
    let t = InvariantLifetime(PhantomData); // InvariantLifetime<'1>(PhantomData<'1>)
    strictly_match2(&t, &phantom);
}

为什么可以存在 '1 那么长,就结合我第一个回答的第二部分,把 PhantomData 和 InvariantLifetime 类型等价成:

struct PhantomData<'p>(*mut &'p ());
struct InvariantLifetime<'id>(PhantomData<'id>);
let ref_unit = &(); // &'static unit -> &'1 unit 协变
let phantom_inner: *mut &'_ () = &mut &() as _; // *mut &'1 () 不变
let phantom: PhantomData<'_> = PhantomData(phantom_inner); // PhantomData<'1>
let t: InvariantLifetime = InvariantLifetime(phantom); // InvariantLifetime<'1>(PhantomData<'1>)

所以和 fn j(c: Cell<&'_ str>) 没什么不同。该讲的都讲了,没什么新鲜的。

作者 leolee0101 2022-08-16 21:00

哎呀,忙中出错,犯低级错误,str 和 string 弄混了。
再次麻烦看下这个 测试

苦瓜小仔 2022-08-16 20:30

情况一

一个简单的极端情况:'a 为 'static,代码通过,这不难理解,所有引用都保持 'static。

fn j(c: Cell<&'static str>) {
    let s = "aa"; // &'static str
    let cell: Cell<&'static str> = Cell::new(s);
    strictly_match1(&cell, &c);
}

fn main() {
    let refincell: &'static str = "";
    let cell: Cell<&'static str> = Cell::new(refincell);
    j(cell);
}

情况二

让 'a 不为 'static, 代码通过

fn j(c: Cell<&'_ str>) {// Cell<&'1 str>
    let s = "aa"; // &'static str -> &'1 str 因为协变,而且 'static 是任何生命周期的子类型
    let cell: Cell<&'_ str> = Cell::new(s); // Cell<&'1 str>
    strictly_match1(&cell, &c); // strictly_match1(&Cell<&'1 str>, &Cell<&'1 str>) 编译通过
}

fn main() {
    let refincell: &'static str = "";
    let cell: Cell<&'static str> = Cell::new(refincell);
    j(cell);
}

情况三

refincell 不为 'static,代码通过

fn j(c: Cell<&'_ str>) {// Cell<&'1 str>
    let s = "aa"; // &'static str -> &'1 str 因为协变,而且 'static 是任何生命周期的子类型
    let cell: Cell<&'_ str> = Cell::new(s); // Cell<&'1 str>
    strictly_match1(&cell, &c); // strictly_match1(&Cell<&'1 str>, &Cell<&'1 str>) 编译通过
}

fn main() {
    let s = String::new();
    let refincell: &'_ str = &s; // &'outer str(一定不是 &'static str)
    let cell: Cell<&'_ str> = Cell::new(refincell);
    j(cell);
}

这显示了单独分析 fn j(c: Cell<&'_ str>) 的好处,无需看到 '_ 在函数外具体是什么生命周期,因为对于函数 j 来说不重要 —— 函数 j 只需要知道 '_ 是一个外部的、不变的生命周期即可。子类型和型变,赋予的这种“遗忘的能力”。

作者 leolee0101 2022-08-16 19:57

@ 苦瓜小仔 兄,这个例子中 fn j 为什么可以编译?
测试

 fn strictly_match1<'a>(_: &Cell<&'a str>, _: &Cell<&'a str>)
{}
fn j(c: Cell<&str>) {
    let string = String::from("outer: string");
    
    //let cell: Cell<&'static str> = Cell::new("aa");
    let cell: Cell<&'_ str> = Cell::new("aa");
    strictly_match1(&cell, &c);
}
作者 leolee0101 2022-08-16 11:18

嗯,感谢耐心解惑!!

苦瓜小仔 2022-08-16 11:13

在传参时,因为相对'1不变,严格传递'1。那么这时,是编译器已经确定了'1,就是到test_phantom_life调用完成的位置,还是说,此时还是不能确定,还有可能只持续到test_phantom_life内部的某位置。是因为不变,所以在传参时候就必须先确定'1 的确定范围?

从编译结果看:

在传参时,因为相对'1不变,严格传递'1。那么这时,是编译器已经确定了 '1

是的。就是这种情况。

作者 leolee0101 2022-08-16 11:07

嗯,想讨论的就是“InvariantLifetime<'> 的 ' 这个生命周期”。关键就是认定'l 持续到哪儿,以及何时确定。

“'1 来自于函数体外部,让 &ss 活到 '1,意味着 ss 超出函数体仍然被借用”。在传参时,因为相对'1不变,严格传递'1。那么这时,是编译器已经确定了'1,就是到test_phantom_life调用完成的位置,还是说,此时还是不能确定,还有可能只持续到test_phantom_life内部的某位置。是因为不变,所以在传参时候就必须先确定'1 的确定范围?

--
👇
苦瓜小仔: > 来自函数test_phantom_life外,就认为其life end 也在这个函数外部,尽管其实引用在函数内部调用strictly_match之后就不再使用

是的。对于 fn test_phantom_life(phantom: InvariantLifetime<'_>)

参数 phantom 在函数内结束生命周期 —— 在函数内部调用strictly_match之后结束。

但整个问题讨论的不是 phantom 的生命周期,而是 InvariantLifetime<'_>'_ 这个生命周期。

理解错误消息

has type InvariantLifetime<'1>

phantom 的类型为 InvariantLifetime<'1>

argument requires that ss is borrowed for '1

strictly_match(&mut &ss, &phantom); 的参数要求 ss 必须借用到 '1 那么长,即要求 &'1 ss

borrowed value does not live long enough

但 &ss 实际活不了 '1 那么长

ss dropped here while still borrowed

'1 来自于函数体外部,让 &ss 活到 '1,意味着 ss 超出函数体仍然被借用,而 ss 此时被 drop

最关键的地方在于 ^^^ 符号指向的地方:即第 3 条。

苦瓜小仔 2022-08-16 10:59

来自函数test_phantom_life外,就认为其life end 也在这个函数外部,尽管其实引用在函数内部调用strictly_match之后就不再使用

是的。对于 fn test_phantom_life(phantom: InvariantLifetime<'_>)

参数 phantom 在函数内结束生命周期 —— 在函数内部调用strictly_match之后结束。

但整个问题讨论的不是 phantom 的生命周期,而是 InvariantLifetime<'_>'_ 这个生命周期。

理解错误消息

has type InvariantLifetime<'1>

phantom 的类型为 InvariantLifetime<'1>

argument requires that ss is borrowed for '1

strictly_match(&mut &ss, &phantom); 的参数要求 ss 必须借用到 '1 那么长,即要求 &'1 ss

borrowed value does not live long enough

但 &ss 实际活不了 '1 那么长

ss dropped here while still borrowed

'1 来自于函数体外部,让 &ss 活到 '1,意味着 ss 超出函数体仍然被借用,而 ss 此时被 drop

最关键的地方在于 ^^^ 符号指向的地方:即第 3 条。

作者 leolee0101 2022-08-16 09:51

现在还有点疑问是,报错信息,似乎因为引用来自函数test_phantom_life外,就认为其life end 也在这个函数外部,尽管其实引用在函数内部调用strictly_match之后就不再使用。那么是NLL在跨函数边界,不起作用吗?
不然似乎报错信息应该是 'l strictly outlive ss ,和 strictly_match 的 requirement 冲突。

error[E0597]: `ss` does not live long enough
  --> src/main.rs:35:29
   |
31 |  fn test_phantom_life(phantom: InvariantLifetime<'_>)
   |                       ------- has type `InvariantLifetime<'1>`
...
35 |         strictly_match(&mut &ss, &phantom);
   |         --------------------^^^-----------
   |         |                   |
   |         |                   borrowed value does not live long enough
   |         argument requires that `ss` is borrowed for `'1`
...
39 |     }
   |     - `ss` dropped here while still borrowed  
作者 leolee0101 2022-08-15 23:12

关于型变,其实我明白你讲的意思。之前不确定的是InvariantLifetime<'_> life 的start 和 end 。是否只看 life end 。嗯,有的地方还得再捋一捋。

--
👇
苦瓜小仔: 在这里,无论是

fn f(phantom: InvariantLifetime<'_>)

还是

fn f(phantom: &InvariantLifetime<'_>)

结果都是一样的。

直觉就是,'_ 是函数外部的引用(或者说生命周期),而 invariance 必须保持生命周期不变,从而不可能被缩短,所以只能比函数内部引用的生命周期更长,永远不可能等于函数内部引用的生命周期。

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

在这里,无论是

fn f(phantom: InvariantLifetime<'_>)

还是

fn f(phantom: &InvariantLifetime<'_>)

结果都是一样的。

直觉就是,'_ 是函数外部的引用(或者说生命周期),而 invariance 必须保持生命周期不变,从而不可能被缩短,所以只能比函数内部引用的生命周期更长,永远不可能等于函数内部引用的生命周期。

1 2 共 35 条评论, 2 页