Rust / wasm32 / refcell / double borrow_mut() // how to debug?


#1
  1. No, I do not have a minimal failure case.

  2. I am havking on a large proprietary rust codebase. It is compiled to wasm32.

  3. I get a double borrow_mut() error.

  4. Is there anyway to get the two lines where the borrow_mut() happens? Right now, I have no idea how to begin debugging this.


#2

In how many places is borrow_mut() called? Because you could add the wasm equivalent of a println!() before each one to see which one is being called before you get the error?


#3

By luck, I found the bug, but I had 5 borrow_mut(), 17 borrow(), and the real but was not double borrow_mut(), but

borrow_mut() + borrow()

Still, I’m now insanely paranoid anytime I take borrow() or borrow_mut() on an object and then call a member function (instead of directly setting a member).


#4

Is there a good resource on “RefCell” design patterns? My current intuition is to:

  1. instead of having a “big” RefCell over an important struct

  2. have lots of “small” RefCell over each member of the structs I want to be able to mutate

This, in an effort to “make refcell less likely to collide due to making it more finegrained” has the unforunate side effect that it increases the number of borrow() / borrow_mut()


#5

Not sure if there’s an established RefCell usage pattern, but personally, I try to minimize the number of places I call borrow/borrow_mut. Instead, borrow the interior value in some entry point to an API, and then pass the resulting reference (or the Ref handle, if you must) around rather than having multiple places in a call graph attempt their own borrows. This is essentially the same thing you’d want to do with a mutex or some other non-reentrant synchronization mechanism. Passing references around also encodes (somewhat) the state of the program: if an API is taking a reference, then the “lock” is already held.

Working with RefCell otherwise, such as sprinkling independent borrow calls, will almost always lead to the type of error you’ve observed, given a sufficiently complex call graph.


#6

@vitalyd : Is it correct to approximate your technique as:

a few number of “big” RefCells ?


#7

I’m not sure they necessarily need to be “big”, although that may frequently end up being the case. The cell holds the state you’d like to use interior mutability on - that may be big or small, depends on need. I think the key aspect is to find that API/functionality entrypoint, borrow there, and then pass the reference around the other functions participating in the API/functionality. Essentially, you want to avoid sprinkling borrow calls around APIs that interact.

Also, I try really hard to avoid RefCell entirely - doesn’t always work out, but it’s always worth a shot. It’s possible a RefCell is truly needed, but it can oftentimes be a symptom of suboptimal factoring, so it’s a good opportunity to stop and review the design.


#8

My situation is:

  1. I am usings tdwedb.

  2. I am defining Closures (which are attached to event handlers: mouse move, keyboard, resize, etc …). On these handlers, state needs to be modified. I am doing this by having an Rc to the underlying objects. However, since there are more than one Rc, I end up needing them ot be Rc<RefCell<>> to borrow / mutate.

  3. I don’t know if this is good/bad – in all of Rust so far, I have managed to avoid Rc<RefCell<…>> by rethinking the design, but in this particular case – event handlers needing pointers to objects to be mutated – I so far see no other way.


#9

Yeah, GUIs with closures as event handlers can be a case where Rc<RefCell<...>> is easier/practical. I think the jury is still out on how to best design UI frameworks in Rust because the classic approaches (OO widget hierarchies, complex mutable object graphs, etc) don’t jive well with Rust.

Another option might be to use Cell for some types/data that allow for it.

Yet another might be for the event handlers to have a channel/buffer where they send/enqueue a message, indicating what state change needs to happen. If you have an event loop of sorts and deferring state changes to well-known points is workable, you’d apply those state changes in a place that owns the state (so no Rc).