Lifetimes: borrowed from other struct field

Hi, this question must have been asked a thousand times, but I cannot figure out what the best way to do this would be.

I have a struct where I store a value, and I want to store some semi-processed value of the first value as well to speed up computation. The second value requires the first to be around (i.e. the first has to outlive the second). I would like to store both of these in a struct for passing them around. I immediately run into problems because I am not allowed to move the first field around after having borrowed it. So I try to pin it so that it should be stationary, but still this seems to require some unsafe rust. What would be the best way to solve this with a minimum amount of unsafe rust. The code in question is here: roaring-landmask/shapes.rs at pin-geom · gauteh/roaring-landmask · GitHub. The references to the first fields data is most likely pointers in the C library (maybe to heap allocated memory), let us at least assume that.

A minimum example I've created is as follows, but seems spectacularly unsafe.. I am not even sure it is sound:

use std::pin::Pin;
use std::mem::MaybeUninit;

#[derive(Debug)]
pub struct One(u32);
#[derive(Debug)]
pub struct Two<'a>(&'a u32);


#[derive(Debug)]
pub struct Owny<'a> {
    one: Pin<Box<One>>,
    two: MaybeUninit<Two<'a>>,
}

impl<'a> Owny<'a> {
    pub fn new(one: One) -> Owny<'a> {
        let mut ow = Owny {
            one: Box::pin(one),
            two: MaybeUninit::uninit()
        };

        ow.two = unsafe { std::mem::transmute(MaybeUninit::new(Two(&ow.one.0))) };

        ow
    }
}

fn main() {
    println!("Hello, world!");

    let ow = Owny::new(One(34));

    println!("owny: {:#?}", ow);
    println!("two: {:#?}", unsafe { ow.two.assume_init() });
}

playground

This is indeed incredibly unsafe, and all I can say is "don't do it". The correct unsafe version looks like this:

#[derive(Debug)]
pub struct One(u32);
#[derive(Debug)]
pub struct Two<'a>(&'a u32);


#[derive(Debug)]
pub struct Owny {
    one: *mut One,
    two: Two<'static>,
}

impl Drop for Owny {
    fn drop(&mut self) {
        unsafe {
            drop(Box::from_raw(self.one));
        }
    }
}

impl Owny {
    pub fn new(one: One) -> Owny {
        let boxed = Box::new(one);
        let boxed_ptr = Box::into_raw(boxed);
        
        let two = Two(unsafe { &(*boxed_ptr).0 });
        
        Owny {
            one: boxed_ptr,
            two: two,
        }
    }
    
    pub fn one(&self) -> &One {
        unsafe {
            &*self.one
        }
    }
    
    // explicitly shorten inner lifetime
    pub fn two<'a>(&'a self) -> &'a Two<'a> {
        &self.two
    }
}

fn main() {
    let owny = Owny::new(One(10));
    
    println!("{:?}", owny.one());
    println!("{:?}", owny.two());
}

However be aware that the existence of the reference in two means that the integer one field must not be modified. This is as described in this article that notes the following:

In the case of an immutable reference, it is guaranteed that the value behind the reference is not modified while the reference exists. Note that an immutable reference also prevents modification from other places while you hold the reference. This makes it easy to verify safety: If the value is completely immutable, there is no possibility of data races whatsoever.

Thank you very much! I thought that Pin would somehow be involved in a solution, but apparently not.

The immutability is verified through making the fields private, and only provide an immutable getter for one?

Pin would only be needed in the case where the field with data that other fields points at is not behind a Box, but directly in the struct. In this case you need Pin to have the owner of Owny promise you to not move the Owny struct. Furthermore, the Pin would not be inside Owny, but around it.

In this case yes.

Ok, so Pin could be used to generalize this to a more stack-based solution. Trying to understand Pin: docs mention that Pin makes guarantees about the object at P, not P itself, i.e. the Pin can be moved around/changed but the pointed-to-object P cannot? Would it then not be sufficient to Pin the one field in this case? Since the integer will then always be located at the same place, which is what two requires (it is not important whether the Owny instance is stable)?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.