rustborrow-checker

How can I tell the compiler to release a borrow in a struct without dropping the entire struct?


I have the following struct which represents a plan for a numeric computation:

pub struct NfftPlan<'a> {
    x: Option<&'a [f64]>,
    f_hat: Option<&'a [Complex64]>,
    // ...
}

It has a set_f_hat method:

pub fn set_f_hat(&mut self, f_hat: &'a [Complex64]) {
    self.f_hat = Some(f_hat);
}

and an execute method:

pub fn execute(&self) -> Vec<Complex64>

which uses f_hat immutably.

I want to use this in the following way:

let mut f_hat = vec![1,2,3,4];
let plan = NfftPlan::new()
plan.set_f_hat(&f_hat);
plan.execute();
f_hat[0] = 3; // Change f_hat to a new value
plan.execute(); //New computation

This fails because I cant mutate f_hat while plan still exists. Is there a way for plan to release the borrow to f_hat which would allow me to mutate the f_hat vector? Something like this:

releasedata(&self) {
    self.f_hat = None
} //Now the compiler forgets that plan would hold an borrow to f_hat

I know that Rust does not allow me to change the vector while a borrow to it exists, in this case via the f_hat reference in the NfftPlan struct. I would like a way to tell the compiler to drop the reference to the vector in the NfftPlan struct without dropping the entire struct.


Solution

  • Explanation

    How can I tell the compiler to release a borrow

    You cannot, period. This isn't something you "tell" the compiler, the compiler knows all. You can only completely stop using the reference.

    without dropping the entire struct

    Dropping doesn't clear the borrow, only the borrow no longer being used does, which may happen from the drop.

    f_hat[0] = 3; // Change f_hat to a new value
    plan.execute(); //New computation
    

    This is exactly one of the types of code that Rust tries to prevent. It is not obvious at all that plan.execute() should return a different value because some apparently unrelated value has changed.

    Solutions

    Encode it in the type system

    I'd structure my types to reflect how they need to be used, creating throwaway values that can execute only once everything has been combined together. This means that the structure that borrows f_mut is dropped as soon as it's done; note how this removes the Option completely:

    fn main() {
        let mut f_hat = 42;
    
        let plan = Plan::default();
        plan.set_f_hat(&f_hat).execute();
    
        f_hat = 3;
        plan.set_f_hat(&f_hat).execute();
    }
    
    #[derive(Debug, Default)]
    struct Plan<'a> {
        x: Option<&'a i32>,
    }
    
    impl<'a> Plan<'a> {
        fn set_f_hat(&self, f_hat: &'a i32) -> PlanPlus<'a> {
            PlanPlus { x: self.x, f_hat }
        }
    }
    
    #[derive(Debug)]
    struct PlanPlus<'a> {
        x: Option<&'a i32>,
        f_hat: &'a i32,
    }
    
    impl<'a> PlanPlus<'a> {
        fn execute(&self) {}
    }
    

    Use interior mutability and reference counting

    use std::{cell::Cell, rc::Rc};
    
    #[derive(Debug, Default)]
    struct Plan<'a> {
        x: Option<&'a i32>,
        f_hat: Option<Rc<Cell<i32>>>,
    }
    
    impl<'a> Plan<'a> {
        fn set_f_hat(&mut self, f_hat: Rc<Cell<i32>>) {
            self.f_hat = Some(f_hat);
        }
        fn execute(&self) {}
    }
    
    fn main() {
        let f_hat = Rc::new(Cell::new(42));
    
        let mut plan = Plan::default();
        plan.set_f_hat(f_hat.clone());
        plan.execute();
    
        f_hat.set(3);
        plan.execute();
    }
    

    Recognize that the member is mutable

    #[derive(Debug, Default)]
    struct Plan<'a> {
        x: Option<&'a i32>,
        f_hat: Option<&'a mut i32>,
    }
    
    impl<'a> Plan<'a> {
        fn f_hat(&mut self) -> &mut Option<&'a mut i32> {
            &mut self.f_hat
        }
    
        fn execute(&self) {}
    }
    
    fn main() {
        let mut f_hat = 42;
    
        let mut plan = Plan::default();
        *plan.f_hat() = Some(&mut f_hat);
        plan.execute();
        **plan.f_hat().as_mut().unwrap() = 3;
        plan.execute();
    }
    

    See also: