Lifetimes wrapped inside PhantomData

use std::marker::PhantomData;

struct Struct1(u32);

impl Struct1 {
    fn new() -> Self { Self(0) }

    fn take_mut(&mut self) {
        println!("{}", self.0);
    }
}

struct Struct2<'a>(PhantomData<&'a ()>);

impl<'a> Struct2<'a> {
    fn from_first(_: &'a Struct1) -> Struct2<'a> {
        Struct2(PhantomData)
    }
}

fn main() {
    let mut first = Struct1::new();
    let second = Struct2::from_first(&first);

    // if this line is commented out, the code compiles
    first.take_mut();

    // EDIT: updated after first two comments
    std::mem::drop(second);
    std::mem::drop(first);
}

Intention of this code is to define drop order of Struct1 and Struct2 by using lifetimes. However, it seems that borrow checker doesn't let the code compile even though there is no actual borrow inside Struct1.

Is there a way to make this code compile?

No, not if you want to mutably access first.

Did you mean to mark first as mutable? Rust Playground

- let first = Struct1::new();
+ let mut first = Struct1::new();

ok, sorry. I need to modify my example. This is not what I wanted :man_facepalming:

@RustyYato @alice I've updated the example now.

Lifetimes are done with a purely syntactic analysis. And because you are telling Rust that Struct2 may hold references into Struct1 (by tying their lifetimes together), it's invalid for you to call take_mut while that phantom borrow is active.

You can return a reference with the from_first call.

impl<'a> Struct2<'a> {
    fn from_first(a: &'a mut Struct1) -> (&'a mut Struct1, Struct2<'a>) {
        (a, Struct2(PhantomData))
    }
}

And use it instead of the original variable.

let (first, second) = Struct2::from_first(&mut first);
1 Like

interesting, but this doesn't produce the desired compiler enforced drop order of Struct1 and Struct2.

@mversic I'm pretty sure it does, actually.

yeah it does, you are right