Search code examples
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:

    • You're saying that something "seems like undefined behaviour" However, the nice thing about Rust is that, as long as you're not using unsafe{}, it will be perfectly fine. As far as I know, there should be nothing to worry about there.
    • You say you wrapped both in Option<Rc<RefCell<T>>>, but why couldn't you just make a single struct that contains a Box/RC of each without them having to reference each other? I presume that that is because parts of it are optional, but, given how they interact with each-other, I doubt that that optionality would work either way. It looks to me like you want one struct that contains both the language contexts that has the functions for both in it.
    • If you want more information / something else than "change your architecture" please provide a minimum working (though it does not need to compile) example we can use to look at and see exactly what is going on.

    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.