简体   繁体   中英

How do I temporarily borrow of Reference from a structure?

I am trying to temporarily replace a reference of a structure inside of a block. However, even if I restore the original reference, the borrow checker is complaining about lifetime problems. The code that follows shows the problem:

// ExpensiveData is only a placeholder for a much more complex data structure
struct ExpensiveData(i32);
struct ReferenceToED<'a>(&'a mut ExpensiveData);

impl<'a> ReferenceToED<'a> {
    fn new(content: &'a mut ExpensiveData) -> Self {
        ReferenceToED(content)
    }

    fn replace(&mut self, new_content: &'a mut ExpensiveData) -> &'a mut ExpensiveData {
        std::mem::replace(&mut self.0, new_content)
    }

    fn get_ref_ed(&self) -> &ExpensiveData {
        self.0
    }

    // Other methods removed for clarity ...
}

fn check_ref_struct() {
    let mut ed = ExpensiveData(2);
    let mut ref_ed = ReferenceToED::new(&mut ed);

    {
        let mut ed = ExpensiveData(5);

        // Remember the old reference And exchange it with a new one
        let tmp = ref_ed.replace(&mut ed);
        assert_eq!(5, ref_ed.get_ref_ed().0);

        // Restore the old reference
        ref_ed.replace(tmp);
        assert_eq!(2, ref_ed.get_ref_ed().0);
    }

    // Although restored I get an error in this line
    assert_eq!(2, ref_ed.get_ref_ed().0);
}

the error message is as follows:

error[E0597]: `ed` does not live long enough
  --> src\lib.rs:29:34
   |
29 |         let tmp = ref_ed.replace(&mut ed);
   |                                  ^^^^^^^ borrowed value does not live long enough
...
35 |     }
   |     - `ed` dropped here while still borrowed
...
38 |     assert_eq!(3, ref_ed.get_ref_ed().0);
   |                   ------ borrow later used here

Questions:

  1. How can I convince the borrow checker, that this is safe code? (Of cause besides using unsafe code)
  2. Is there a typical pattern to follow how to handle these type of problems?

After a while of experimentation I came to the solution below. I used a structure ReferenceToEDManager for managing the temporary exchanged data:

pub struct ReferenceToEDManager<'reference_to_ed, 'data, 'tmp> {
    reference_to_ed: &'reference_to_ed mut ReferenceToED<'data>,
    orig_data: Option<&'data mut ExpensiveData>,
    _pd_td: marker::PhantomData<&'tmp mut ExpensiveData>,
}

impl<'reference_to_ed, 'data, 'tmp>
    ReferenceToEDManager<'reference_to_ed, 'data, 'tmp>
{
    pub fn new(
        reference_to_ed: &'reference_to_ed mut ReferenceToED<'data>,
        new_data: &'tmp mut ExpensiveData,
    ) -> Self {
        let new_data_ptr = ptr::NonNull::new(new_data).unwrap();

        // SAFETY: It is possible, to replace the ExpensiveData reference with a
        // shorter living reference of new_data in ReferenceToED, because
        // the lifetime of self is at most the minimal lifetimes of:
        //   - orig_data and
        //   - reference_to_ed
        // and because the Drop implementation of Self restores the old
        // configuration of reference_to_ed
        let orig_data = reference_to_ed
            .replace(unsafe { &mut *new_data_ptr.as_ptr() });
        Self {
            reference_to_ed,
            orig_data: Some(orig_data),
            _pd_td: marker::PhantomData,
        }
    }

    pub fn get_reference_to_ed(&mut self) -> &mut ReferenceToED<'tmp> {
        let reference_to_ed_pointer =
            ptr::NonNull::new(self.reference_to_ed)
                .unwrap()
                .cast::<ReferenceToED<'tmp>>();

        // SAFETY: It is safe to return a reference to the `ReferenceToED`
        // with tmp_data lifetime for the contained data, because during
        // construction of self a reference with this lifetime has been
        // inserted in the `ReferenceToED`
        unsafe { &mut *reference_to_ed_pointer.as_ptr() }
    }
}

impl Drop for ReferenceToEDManager<'_, '_, '_> {
    /// Replaces the temporary reference of `ExpensiveData` whith the original one.
    ///
    /// # Safety
    ///
    /// `ReferenceToED::replace(...)` shall replace the referenced
    /// `ExpensiveData` with the one supplied as argument.
    fn drop(&mut self) {
        let orig_value = self.orig_data.take().unwrap();
        self.reference_to_ed.replace(orig_value);
    }
}

#[cfg(test)]
mod test {
    use super::super::expensive_data::{ExpensiveData, ReferenceToED};
    use super::ReferenceToEDManager;
    #[test]
    fn check_ref_struct() {
        let mut ed = ExpensiveData(2);
        let mut ref_ed = ReferenceToED::new(&mut ed);
        {
            let mut ed = ExpensiveData(5);
            // Remember the old reference And exchange it with a new one
            let mut ed_manager =
                ReferenceToEDManager::new(&mut ref_ed, &mut ed);
            // NOTE: It is impossible to use ref_ed from this point on
            // any more, thanks to Rust borrowing rules!
            // assert_eq!(5, ref_ed.get_ref_ed().0);
            let ref_ed_ref = ed_manager.get_reference_to_ed();
            assert_eq!(5, ref_ed_ref.get_ref_ed().0);

            // Restore the old reference
        }
        // Although restored I get an error in this line
        assert_eq!(2, ref_ed.get_ref_ed().0);
    }
}

I am rather shure, it is impossible to write this code without any unsafe blocks, because it needs some manipulations of lifetimes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM