Mechanism to achieve temporary immutability?


#1

Hi all,

I’m struggling with a design problem and was hoping someone might have a suggestion of how to solve it. Here’s some pseudocode that lays out what I’m trying to accomplish:

// I have a structure that stores some complex state.
struct State { ... } 

// It implements a trait.
trait MyTrait { ... }
impl MyTrait for State { ... }

// I have a helper object that is somewhat annoying to build that works with
// a Vec of objects implementing MyTrait.
struct Helper { v: Vec<&MyTrait>, ... }

// I want to do something in the following pattern:
fn do_stuff() {
   let mut state = State:new(...);
   let mut helper = annoying_stuff(&mut state as &mut MyTrait);
   helper.do_many_things();
   state.interrogate_status();
   helper.do_more_things();
}

The problem is that the interrogate_status line is illegal — state is mutably borrowed by helper, so I can’t access it as long as helper lives. If all I needed was the do_many_things() line, I could put the creation of helper in a block and force it to be deallocated by the time I wanted to interrogate_status(). But I want to reuse helper later, so I can’t do that.

Note that I can’t pull state out of helper through a method since its type has been erased by its conversion into a trait object.

I think that I could accomplish what I want with some kind of mechanism to temporarily make helper immutable. If I could “suspend” the mutability of helper between do_many_things() and do_more_things(), then I could borrow state immutably and interrogate its status.

But I’m not able to devise a mechanism to do this. Am I missing something?

I see other solutions:

  1. Hack up MyTrait to implement the functions I want even though they don’t really belong on the trait; then access state as a MyTrait trait object through helper.
  2. Destroy helper after do_many_things() and recreate a new equivalent object for the do_more_things() call. This wouldn’t actually be thaaat much of a hassle but it feels very inelegant.

Any suggestions? Thanks!


#2

Sounds like you want a RefCell (single-thread RW “lock”). You could either make all methods on MyTrait take self by immutable reference &self and have your implementations use RefCells internally for interrior mutability or you could pass in &RefCell<MyTrait> to annoying_stuff and have annoying_stuff call RefCell<MyTrait>::borrow_mut as needed.


#3

Yeah, I think that would work … it feels awfully unsatisfactory, though. I feel like there must be some way to structure the code so that helper becomes immutable for a little while, then I make it mutable again. I could believe that there’s not a language feature that directly enables this, but I feel like there must be a way to structure things to achieve the effect.


#4

Well, that’s really the point of RefCell; it’s for cases where the compiler can’t check your borrowing. The problem is not the mutability of helper, it’s that the borrow checker doesn’t allow more than one mutable borrow (of state) at a time. Rust may eventually get some way to temporarily suspend a borrow (called a non-lexical borrow) but I don’t think that would even help in this case.

One alternative is downcasting. That is, you could define MyTrait as trait MyTrait: Any { ... }, pull the &mut MyTrait out of helper, and then downcast it to an &mut State. However, this seems even more hacky.

Now that I think of it, you could also put the “status” in a Cell (or RefCell) on the stack. Something like:

let status = Cell::new(Status::Start); // You'd have to use a `RefCell` if `Status` isn't `Copy`.
let mut state = State:new(&status, ...);
let mut helper = annoying_stuff(&mut state as &mut MyTrait);
helper.do_many_things(); // Calls status.set(...) internally.
do_something(status.get());
helper.do_more_things();

Also, is there no common interrogate_status functionality?


#5

If the helper kept a direct state reference instead of a trait object, it could loan that out temporarily, even mutably:

impl Helper {
    fn state(&self) -> &State { ... }
    fn state_mut(&mut self) -> &mut State { ... }
}

The self borrows will extend to the lifetime of the returned state reference. When that scope ends, the helper can be used again.


#6

Thanks for all the suggestions. I guess the main takeaway for me is that, no, I’m not missing some obvious and sensible way to accomplish what I originally asked. Given that, In the end, I felt like it was cleanest to take the route of recreating the helper object:

let mut state = ...
{
    let mut helper = annoying_stuff(...);
    helper.do_many_things();
}
do_something(state);
{
    let mut helper = annoying_stuff(...);
    helper.do_more_things();
}

I added a (meta-?)helper struct that basically encapsulates the work in annoying_stuff while also letting the outer layers of the code access the specific state information it needs. I still have an intuition that in principle I could be reusing the helper struct, but the final code actually comes across as being quite reasonable.