How RefCell could result in thread panic?

I am reading the documentation on RefCell and Cell to better wrap my head around interior mutability and when to use it.

I came across this statement and I was wondering if anyone could help me unpack it a bit.

RefCell<T> uses Rust's lifetimes to implement 'dynamic borrowing', a process whereby one can claim temporary, exclusive, mutable access to the inner value. Borrows for RefCell<T> s are tracked 'at runtime', unlike Rust's native reference types which are entirely tracked statically, at compile time. Because RefCell<T> borrows are dynamic it is possible to attempt to borrow a value that is already mutably borrowed; when this happens it results in thread panic.

It seems to me that if this is only used in single threaded applications, what is the situation where a thread panic need to arise. In a single threaded application there should be no fear of data races, and no simultaneous borrows, so what is the situation that would cause a thread panic?

I can imagine it's more for debugging more complex programs. For example, (for whatever reason) suppose you have a single-threaded runtime that's being interacted-upon by a multithreaded runtime. With proper implementation, this could safely be done in Rust especially with mutexes on the interaction layer between the multithreaded -> single threaded boundary. However, if someone makes a communicator that allows sending two jobs to the single-threaded environment in concurrence, then the RefCell could panic. It's really there to help insantiate good programming practice. This is at least one way I can see that the panic could be useful for debugging, but I'm sure there are other ways

The panic is not to protect you from data races, rather it's to protect you from modifying things while you have a reference to them. Observe:

fn main() {
    let cell = RefCell::new(vec![5]);
    
    // Get a reference to the five
    let ref1 = cell.borrow();
    let ref_to_5 = &ref1[0];
    
    // Clear the list.
    let mut ref2 = cell.borrow_mut();
    ref2.clear();
    
    // Oops, the reference is invalidated.
    println!("{}", ref_to_5);
}

playground

The above will compile but panic. It compiles due to the fact that borrow_mut takes an immutable reference to the RefCell — if you wrote the example above without a RefCell, you would get a compile error due to needing a mutable reference to clear the vector (playground).

2 Likes

Boom!!! This makes sense, thank you for the explanation!

1 Like

It protects from both then (but only in the sense that bad logic will be shown during program execution)

Data races can only happen when multiple threads are involved. RefCell can only be used on a single thread at any given point in time.

You are protected from data races, but not by the panic: That's the job of missing Send and Sync impls.

Hence the example of mixing single/multi-threaded RT's

Correct

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.