Unlikus
Unlikus

Reputation: 1858

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.

Upvotes: -1

Views: 268

Answers (2)

Erciyuanshagou
Erciyuanshagou

Reputation: 26

There is indeed a simple workaground other than unsafe, though you need to reconstruct a new NfftPlan.

fn releasedata(self)->NfftPlan {
    NfftPlan{
        f_hat:None,
        x:self.x,
        //...
    }
}

And call it like this:

let mut f_hat = vec![1,2,3,4];
let mut plan = NfftPlan::new();
plan.set_f_hat(&f_hat);
plan.execute();

plan = plan.releasedata();
//Now the compiler forgets that plan would hold an borrow to f_hat

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

If you are looking for a solution that can also avoid reconstructing, I am seeking for that solution too.

Upvotes: 0

Shepmaster
Shepmaster

Reputation: 431779

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:

Upvotes: 2

Related Questions