Sharing state between APIs in cdylib

Hello experts,

I am trying to implement a shared library in Rust (essentially porting from C, so I have to maintain the compatibility for existing applications).

I know Rust discourages shared global state and recommends to passaround the shared data like ctx back to the calling client and then make it part of the calls, but unfortunately, I have to maintain portability to existing apps, so that is not an option.

What is the best way to handle this. There are many articles, QnA etc., and nothing seems to perfectly work.

I have tried OnceLock that seems to leak some memory and does not solve it perfectly (especially cleaning up - terminate fn below).

Could you share your expert opinions on the best way, and if there are existing code solving this problem, point to them please?

Roughly, the code looks like this. The problem seems to also originate from having to store dyn Trait

// trait.rs
pub trait Trait {
    fn t_process(&self);
}

// file_type1.rs
pub FileType1 {
    field1: type1
    field2: type2
}

impl FileType1 {
   pub fn init(f1: type1, f2: type2) -> Self {
   }

   // other methods
}

impl Trait for FileType1 {
    fn t_process(&self) {
    }
}

// file_type2.rs
pub FileType2 {
    field1: type1
    field2: type2
}

impl FileType2 {
   pub fn init(f1: type1, f2: type2) -> Self {
   }

   // other methods

}

impl Trait for FileType1 {
    fn t_process(&self) {
    }
}

// handler.rs
pub struct Handler {
    inst: Box<dyn Trait + Send + Sync>
}

impl Handler {
    pub fn init(field0: inst_type, field1: type1, field2: type2) -> Self {
        // init self.inst based on field0 passed
    }

    pub fn f_process(&self) {
        self.inst.t_process()
    }

    pub fn terminate() {
       // cleanup
       // two problems - if I use static OnceLock, I cannot pass &mut self
       // I cannot clean Box<dyn Trait> by getting into_raw and freeing it up
    }
}

//lib.rs
pub unsafe extern "C" init(field0: inst_type, field1: type1, field2: type2) {
// init handler
}

pub unsafe extern "C" f_process() {
//call handler.f_process()

}

&mut in Rust is exclusive, but it's not the only way to mutate data. & can mutate via Mutex or RwLock, so you could have a global static Mutex<Option<Box<…>>> and set it to None by locking the mutex.

Does the C library allow you to have void* user data? It's a common pattern for decently-designed ones, and that can be used to access a specific non-global instance of Rust's thin Box (trait objects and closures have to be double-boxed).

5 Likes

Thanks @kornel.

Does the C library allow you to have void* user data?

Unfortunately, no. That would have made my life simple to pass around the state information without worrying much.

& can mutate via Mutex or RwLock , so you could have a global static Mutex<Option<Box<…>>> and set it to None by locking the mutex.

Yes, I did that - along with OnceLock - something like OnceLock<Option<Mutex<Handler>>> setting the OnceLock mutable global static instance to None in terminate, but does that really do the cleanup? Because OnceLock static global instances do not call Drop, so the Box instances in handler do not get freed up. Setting this to None only satisfies the state, but not actually free the memory resource. Is my understanding correct? Further, calling init again allocates new Box or memory resource.

Is my concern valid, or setting OnceLock.set(None) triggers a memory free up too, so I don't need to worry too much.

if your type implement Drop to do the cleanup.

when you set an Option<T> to None, if the old value is Some(x), then x will be dropped.

2 Likes

You don't need OnceLock.

(Quick and dirty example. There's a good chance you want something besides poisoning panics.)

3 Likes

you should not use OnceLock in this case, OnceLock, as its name suggested, is for values that is initialized once, lazily.

in your case, it is some global state that can be initalized and destroyed multiple times, I think Mutex<Option<T>> would be more suitable.

3 Likes

Thanks @nerditation. I will try this - this mostly should suffice.