Interior mutability and C API

(Sorry if it's a stupid question, I'm fairly new to Rust)

I'm working with a Rust binding to a C library, and I'm not sure if it's implemented correctly. This library is gccjit, but I think it will be more educative if I express it as a general problem.

The C library is a collection of functions and data types. Some functions are used to create an instance, these return "owning pointer"; some functions are used to mutate those data structures, they take this pointer. I would normally design the binding as follows:

struct TheType {
    // the pointer to the corresponding C type
    ptr: *mut c_void,
}

impl TheType {
    fn new() -> Self { /*...*/ }

    // Has to be mut because the underlying C function mutates the structure 
    fn set_some_property(&mut self, data: Data)  { 
        unsafe { (c_set_function(self.ptr)) }
    }

    // Doesn't have to be mut because the underlying C function does not mutate the structure 
    fn get_something(&self) -> Something { 
        unsafe { (c_get_function(self.ptr)).into() }
    }
}

Due to the way this library is used, I would like to have all methods take &self to avoid fighting the borrow checker. (This is how this library is implemented now. I suspect it's unsound).

My questions are:

  1. Is my suspicion that &self methods that lead to logically mutating the contents are unsound?
  2. If yes, would wrapping the ptr field in Cell fix the issue?

N.B: the library is inherently single-threaded and I don't care whether the Rust binding types are thread safe or not.

1 Like

If you access the pointer solely by FFI and never create a rust reference to it, I believe using UnsafeCell to be unnecessary.

This stackoverflow discussion might be of interest.

5 Likes

I agree, if you're only storing a pointer the C library gives you, there's no Rust references, so any mutation is going to be happening in C code, governed by C's rules. There might be other reasons to add a &mut self to some of the mutating APIs in the wrapper struct, though: say e.g. one &self API returns a pointer to some data, but subsequent use of a mutating API may invalidate that pointer. Rust can help the wrapper struct's users avoid UB in that situation where C could not.

This is fine. A raw pointer boundary has a similar effect as UnsafeCell.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.