Search code examples
rustcallbackhookffi

Accessing global state from a callback in rust


I am learning rust and I've been trying to rewrite a project that I did in C# in rust, and I got stuck trying to access global state from a callback,

Is there a simple way to do that in rust? Keeping in mind the I can't add new parameter to the callback.

eg:

use std::collections::HashMap;
use std::time::instant;
use lib::bar;

struct Struct2{
    foo: bar,
    word: String,
}

struct GlobalState{
    running: bool,
    now: Instant,
    map: HashMap<String, Struct2>,
    hook_id: usize,
    current_id: String,
}

impl GlobalState{
    fn init() -> Self{
        let hook_id = unsafe {set_ext_hook(HOOK_ID, system_hook)};
        // Omitted initialization code.
    }
    // Omitted state mutation functions.
}

unsafe extern "system" fn system_hook(ext_param1:usize, ext_param2: usize) -> isize {
    // use global state here
}

I tried using crates such as lazy_static and once_cell, but they didn't work because the external struct that I use (lib::bar in this example) "cannot be sent between threads safely"

My code so far is single threaded (I plan on using a different thread for the program's gui when I implement it)

Any help is appreciated, thanks.


Solution

  • You seem to be dealing with data that is neither Send nor Sync, so Rust won't allow you to place it in a global, even inside a mutex. It's not clear from the question whether this is a result of lib::bar being genunely thread-unsafe, or just the unintended consequence of its use of raw pointers under the hood. It is also unclear whether you are in the position to modify lib::bar to make its types Send and Sync.

    Assuming most conservatively that lib::bar cannot be changed, and taking into account that your program is single-threaded, your only safe option is to create a thread-local state:

    use std::cell::RefCell;
    use std::thread_local;
    
    struct Foo(*const i32); // a non-Send/Sync type
    
    struct GlobalState {
        foo: Foo,
        data: String,
        mutable_data: RefCell<String>,
    }
    
    thread_local! {
        static STATE: GlobalState = GlobalState {
            foo: Foo(std::ptr::null()),
            data: "bla".to_string(),
            mutable_data: RefCell::new("".to_string()),
        };
    }
    

    You can access that state (and modify its interior-mutable pieces) from any function:

    fn main() {
        STATE.with(|state| {
            assert_eq!(state.foo.0, std::ptr::null());
            assert_eq!(state.data, "bla");
            assert_eq!(state.mutable_data.borrow().as_str(), "");
            state.mutable_data.borrow_mut().push_str("xyzzy");
        });
        STATE.with(|state| {
            assert_eq!(state.mutable_data.borrow().as_str(), "xyzzy");
        });
    }
    

    Playground

    Note that if you try to access the "global" state from different threads, each will get its own copy of the state:

    fn main() {
        STATE.with(|state| {
            state.mutable_data.borrow_mut().push_str("xyzzy");
        });
        std::thread::spawn(|| {
            STATE.with(|state| {
                // change to "xyzzy" happened on the other copy
                assert_eq!(state.mutable_data.borrow().as_str(), "");
            })
        })
        .join()
        .unwrap();
    }
    

    Playground