Dump all refcell borrow's?

#1
  1. I’m using Rust/Wasm32 and once again running into a weird, unexpected
Some(already borrowed: BorrowMutError), location: Location { file: "src/libcore/result.rs", line: 999, col: 5 } }
  1. I have tracked it down to a single line: println before the line prints, println after the line fails.

  2. What I don’t have however, is a “context” of wtf is going on. Is there some way (I’m okay with unsafe black magic solutions) that somehow dumps a list of all RefCell’s that are already borrow() or borrow_mut() ?

#2

No, there is not

1 Like
#3

There are, however, attempts to build alternatives to RefCell that can detect more errors at compile-time, resulting in less panics at runtime. But as always with library-based static analysis, the code becomes more complex and the technique is not applicable in all circumstances.

1 Like
#4
  1. If a single thread does a nested “borrow/borrow_mut”,
    it’d be nice if it could dump the stack frames and point to the two frames that are doing the borrow_mut();

  2. The theorist would say: by the halting problem, you can’t check dynamic properties at compile time.

  3. However, practically, if there was a linter tool that, for each function, returned the SET of objects that may be borrowed, it’d be hugely helpful.

I tracked down my problem. It goes something like this:

3a. An object, for cleanup purposes, in it’s drop handler, calls something that calls borrow_mut(). (To notify the object is being dropped.)

3b. I have a HashMap<k, Object_with_drop_handler> which looks innocent enough – and sometimes, when updating items in this hashmap, objects gets dropped, triggering a nested borrow_mut() [from the thread too!]

#5

Here is a hacky debugging tool for refcell issues:

/// Put this at the root of the crate (lib.rs / main.rs)
::cfg_if::cfg_if! { if #[cfg(debug_assertions)] {
    #[derive(
        Debug,
        Clone,
        PartialEq, Eq,
        PartialOrd, Ord,
    )]
    struct RefCell<T> {
        ref_cell: ::core::cell::RefCell<T>,

        last_borrow_context: ::core::cell::Cell<&'static str>,
    }

    impl<T> RefCell<T> {
        fn new (value: T) -> Self
        {
            RefCell {
                ref_cell: ::core::cell::RefCell::new(value),
                last_borrow_context: ::core::cell::Cell::new(""),
            }
        }
    }

    macro_rules! borrow {(
        $wrapper:expr
    ) => ({
        let wrapper = &$wrapper;
        if let Ok(ret) = wrapper.ref_cell.try_borrow() {
            wrapper
                .last_borrow_context
                .set(concat!(
                    "was still borrowed from ",
                    file!(), ":", line!(), ":", column!(),
                    " on expression ", 
                    stringify!($wrapper),
                ));
            ret
        } else {
            panic!(
                "Error, {} {}",
                stringify!($wrapper),
                wrapper.last_borrow_context.get(),
            );
        }
    })}
    
    macro_rules! borrow_mut {(
        $wrapper:expr
    ) => ({
        let wrapper = &$wrapper;
        if let Ok(ret) = $wrapper.ref_cell.try_borrow_mut() {
            $wrapper
                .last_borrow_context
                .set(concat!(
                    "was still mutably borrowed from ",
                    file!(), ":", line!(), ":", column!(),
                    " on expression ", 
                    stringify!($wrapper),
                ));
            ret
        } else {
            panic!(
                "Error, {} {}",
                stringify!($wrapper),
                wrapper.last_borrow_context.get(),
            );
        }
    })}
    macro_rules! use_RefCell {() => (
        use crate::RefCell;
    )}
} else {
    macro_rules! borrow {(
        $ref_cell:expr
    ) => (
        $ref_cell.borrow()
    )}
    
    macro_rules! borrow_mut {(
        $ref_cell:expr
    ) => (
        $ref_cell.borrow_mut()
    )}
    macro_rules! use_RefCell {() => (
        use ::core::cell::RefCell;
    )}
}}

So, the following buggy code:

fn main ()
{
    use_RefCell!();
    let x = RefCell::new(String::from("Hello"));
    let print_x = {
        let s = borrow!(x); // line 96
        move || {
            eprintln!("x = {:?}", &*s as &str);
        }
    };
    print_x();
    *borrow_mut!(x) += " World!";
    print_x();
}

prints, on a release build (no debug_assertions):

x = "Hello"
thread 'main' panicked at 'already borrowed: BorrowMutError', src/libcore/result.rs:997:5

but, on a debug build, it prints:

x = "Hello"
thread 'main' panicked at 'Error, x was still borrowed from src/main.rs:96:17 on expression x', src/main.rs:102:6
5 Likes