< 返回版块

acodercat 发表于 2020-05-11 10:02

Tags:PhantomData,借用

下面的这段代码,中15行的t(出借方)的生命周期明显小于借用方u,但是为啥编译可以通过呢?我用了PhantomData<&'a T>的

#[derive(Debug)]
    struct User<'a, T: Debug + 'a> {
        age: u8,
        data: *const T,
        _marker: PhantomData<&'a T>
    }

    #[derive(Debug)]
    struct Test {
        v: Box<i32>
    }
    let u;

     {
         let t = Test {
             v: Box::new(1)
         };
         u = User {
             age: 1,
             data: &t as *const Test,
             _marker: PhantomData
         };
    };
    println!("data: {:?}", unsafe { u.data });

但是下面这段代码就可以成功检测到生命周期的问题:

#[derive(Debug)]
    struct User<'a, T: Debug + 'a> {
        age: u8,
        data: *const T,
        _marker: PhantomData<&'a T>
    }

    #[derive(Debug)]
    struct Test {
        v: Box<i32>
    }
    fn create_user(t: &Test) -> User<Test> {
        User {
            age: 1,
            data: t as *const Test,
            _marker: PhantomData
        }
    }
    let u;

     {
         let t = Test {
             v: Box::new(1)
         };
         u = create_user(&t);
    };
    println!("data: {:?}", unsafe { u.data });

报错信息:

error[E0597]: `t` does not live long enough
  --> src/main.rs:89:26
   |
89 |          u = create_user(&t);
   |                          ^^ borrowed value does not live long enough
90 |     };
   |     - `t` dropped here while still borrowed
91 |     println!("data: {:?}", unsafe { u.data });
   |                                     ------ borrow later used here

评论区

写评论
zydxhs 2020-05-17 22:34

User::data 是个原生指针,而原生指针没有生命周期。

Dengjianping 2020-05-11 19:42

PhantomData<&'a T>只是行为像是拥有这个T,但其实并没有。 你可以实现drop trait,看看什么时候drop的,而且你的data: t as *const Test其实已经是空指针了,ptr::read(data),拿到的不是1了

chenwei767 2020-05-11 18:48

@solarsail

同意这个说法

solarsail 2020-05-11 17:54

个人觉得 @whfuyn 说的有道理。data: &t as *const Test 这里是不检查生命周期的,borrow checker 不管 raw pointer。

Different from references and smart pointers, raw pointers:

  • Are allowed to ignore the borrowing rules by having both immutable and mutable pointers or multiple mutable pointers to the same location
  • Aren’t guaranteed to point to valid memory
  • Are allowed to be null
  • Don’t implement any automatic cleanup

-- The Book, Ch19

第一种方式,User 是现场创建的, PhantomData<&'a T> 中的 'at 的生命周期没关系,编译器认为 ok。

第二种方式如 @whfuyn 所说,create_user 函数把参数和返回值的生命周期联系起来了,t: &'a TestUser<'a, Test> 共用了 'a,所以检查不通过。

whfuyn 2020-05-11 16:04

lifetime应该是针对引用而言的,非引用类型T: 'a让我觉得很可疑。 第一段编译通过的代码:

         u = User {
             age: 1,
             data: &t as *const Test,
             _marker: PhantomData
         };

我认为这句data: &t as *const Test根本没有让User<'a, T: Debug + 'a> 'a&t的生命周期一致,甚至有可能为了让编译通过推导出这个'a应该更长。 而第二段代码:

    fn create_user(t: &Test) -> User<Test> {
        User {
            age: 1,
            data: t as *const Test,
            _marker: PhantomData
        }
    }

实际上应该是fn create_user<'a>(t: &'a Test) -> User<Test>,根据The Book上关于生命周期省略的规则 The second rule is if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameter 返回的User的生命周期被推断为和t一致,所以之后才会报错说生命周期不够长。

以上是我个人的看法,水平有限,我不确定对不对,看看别的朋友怎么说。

chenwei767 2020-05-11 13:25

确实是, 通过一个函数构造就可以正常检测到生命周期.

use std::fmt::Debug;
use std::marker::PhantomData;

fn main() {
	let u = {
		let t = Test {
			v: Box::new(1)
		};
		User::<Test>::create_user(&t)
		// User {
		// 	age: 1,
		// 	data: &t as *const Test,
		// 	_marker: PhantomData,
		// }
	};
	println!("data: {:?}", unsafe { &(*u.data).v });
}

#[derive(Debug)]
struct Test {
	v: Box<i32>
}

#[derive(Debug)]
struct User<'a, T: Debug> {
	age: u8,
	data: *const T,
	_marker: PhantomData<&'a T>,
}

impl<'a, T: Debug> User<'a, T> {
	fn create_user(t: &T) -> User<T> {
		User {
			age: 1,
			data: t as *const T,
			_marker: PhantomData,
		}
	}
}
1 共 6 条评论, 1 页