0

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?
HBex
  • 85
  • 6
  • [This question](https://stackoverflow.com/q/42637911), although covering a bit more than what is needed here, should give a few more hints about the problem with this code. Remember that the lifetime of the value bound to `ref_ed` cannot change at compile time, which is why the inner block and the function scope leads to conflicting lifetimes. – E_net4 Jul 18 '20 at 13:35
  • 1
    If the code between `assert_eq!(5, ...` and `Restore the old reference` were to panic, `ref_ed` would be referencing invalid memory when it was dropped. – loganfsmyth Jul 18 '20 at 14:27
  • @E_net4wantsyoutolearn: You answered the *why*, I understand (at least partially), but the question was *how* to achieve the desired effect. – HBex Jul 19 '20 at 18:09
  • @loganfsmyth : Think about removing the last `assert_eq!(2,...)`. The code compiles and is therefor safe. Now think, what happens just before the block closes: The `ref_ed` is in the same state, as it was before entering the block. So, even if the last `assert_eq!(2, ...)` would not be flagged as an error this is completley safe code. I know, that the borrow checker does not know anything about the semantics of the `replace` method and does its decision at compile time (see the comment of @E_net4wantsyoutolearn ) – HBex Jul 19 '20 at 18:17

1 Answers1

0

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.

HBex
  • 85
  • 6