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.
*/
}
First, some notes:
unsafe{}
, it will be perfectly fine. As far as I know, there should be nothing to worry about there.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.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.