rustborrow-checkerunsafemutability

How can I separate these structs to avoid undefined behavior from multiple mutable references?


I'm working on extending an interpreter written in Rust to support executing code in a new format. I have two structs—LanguageAContext and LanguageBContext— where the latter needs mutable access to the former to execute code in Language B.

Currently, the struct LanguageBContext is a member of LanguageAContext. But, it's possible for Language B to call back into Language A, which means that each context must have a mutable pointer to the other. This seems like undefined behavior, since I'm concurrently modifying both a struct and its member.

My initial solution was to separate both structs and wrap them in an Option<Rc<RefCell<T>>>, but this led to runtime errors due to multiple mutable borrows of each struct when chaining callbacks. Additionally, it complicates the implementation for LanguageAContext, requiring changes everywhere that it's used.

What's the best way to separate these two structs while minimizing changes to LanguageAContext and avoiding undefined behavior? The application is single-threaded.

Edit: Here's a MCVE that summarizes the situation. In particular, unsafe is necessary here because the implementation for LanguageB is in C.


struct LanguageAContext {
    /* global definitions for language A */
    lang_b: LanguageBContext,
}
struct LanguageBContext {
    /* global definitions for language B */
}

impl LanguageBContext {
    fn call_function(
        &mut self,
        parent: *mut LanguageAContext,
        function: LanguageBFunction,
        arguments: LanguageBArgs,
    ) {
        function.call(arguments, parent, callback_into_parent)
    }
}

extern "C" fn callback_into_parent(parent: *mut LanguageBContext) {
    let parent_ref = unsafe { &mut *parent };
    /*
       Code that modifies LanguageAContext, which might eventually
       call LanguageBContext.call_function() again.
    */
}

Solution

  • First, some notes:

    EDIT: Great to see you have added those things. Sadly, I won't be able to help you any further: I have no experience interoperating with C in Rust.