When trying to write a safe interface to a C library in Rust I generated all bindings with bindgen and got quite a few functions which, as a first parameter, accept a mutable pointer:
However, as you can see in the implementation, some of those wrappers accept a mutable reference (set_property) while other do not (has_property), and which mutability modifier to use heavily depends on internal C implementation (nothing stops has_property to mutate something on C side).
The question is what Rust compiler might do if I mess those mutability modifiers up? Say I forgot to mark set_property as mutable, can rustc optimize calling this function if I called it somewhere before and rustc knows about that, causing the second call to be removed?
The question is even more dire in get_child context. The object that it returns can be mutated and everything (with the same set_property functions), however it was returned from an immutable object get_child(&self ...). In theory I could use lifetimes in those cases to constrain the returned object and make get_child mutable. But this doesn't really make sense in the context of the library as children can outlive their parents.
If you did write pub fn set_property(&self, value: i32), then you would still have written a (possibly) perfectly fine instance of interior mutability. The memory actually written to is pointed to by a raw pointer, not a Rust reference, so Rust's aliasing-and-mutability rules don't care about it.
And separate from that, functions are allowed to have side effects regardless of what parameters they take, so deleting a function call is only allowed if the compiler knows that the specific function actually has no side effects.
In this case, your wrapped C type is interior mutable from a Rust perspective. This means that you should use &self instead of &mut self, to avoid constraining the Rust program unnecessarily.
If you did write pub fn set_property(&self, value: i32), then you would still have written a (possibly) perfectly fine instance of interior mutability.
Why possibly? Does that mean I can safely make my whole API accept immutable references (at least from the point of a compiler).
“Possibly” because there are further considerations entangled with this.
In particular, Rust's &mut provides exclusive access that may prevent data corruption and UB. However, in your case, you appear to be wrapping a reference counted (or similar) type, so you can end up with multiple handles to the same object:
let o1 = parent.get_child(0);
let o2 = parent.get_child(0);
// now use o1 and o2 simultaneously
Because this is possible, even if you used &mut self in Object's other methods, it wouldn't provide any protection against concurrent accesses to the same object — because you have two pointers that are independent as far as Rust knows. Therefore, there is no advantage to using &mut self. But, also, because this is possible, you must ensure that these pointers are actually safe to use in this way.
In particular, you might want to write
unsafe impl Send for Object {}
unsafe impl Sync for Object {}
to make the Object pointers be considered safe to use from multiple threads. But you can only do this if the C library provides synchronization such that all of the operations — especially Object_release() — can safely be called from multiple threads (not necessarily with a useful outcome, but at least one that does not have a data race or other UB). Many C libraries of this sort do not do this, with the result that you have to use their objects only on the thread you initially created them (or at least move the entire graph of objects all at once, which is a constraint that Rust's types and traits can't enforce for you unless you write a much more constraining API wrapper).