What are the best practices when interfacing with a C library that passes around pointers to refcounted objects (I) and is not entirely thread safe (II).
I. Wrapper and aliasing
The functions g_object_ref/g_object_unref respectively increase and decrease the reference count. The reference count is, unsurprisingly, initialized to one by g_object_new which means that the caller is currently the sole owner of the newly-created reference. When the reference count reaches zero, that is, when g_object_unref is called by the last client holding a reference to the object, the dispose and the finalize class methods are invoked.
So I probably want to create a wrapper
struct Widget {
ptr: *mut GtkWidget,
}
impl Widget {
fn ref_(&mut self) { ffi::g_object_ref(self.ptr) }
fn unref(&mut self) { ffi::g_object_unref(self.ptr) }
pub fn new() -> Widget { Widget { ptr: ffi::gtk_widget_new() } }
pub fn get_foo(&self) -> Foo { ffi::gtk_widget_get_foo(self.ptr) }
pub fn set_foo(&mut self, foo: Foo) { ffi::gtk_widget_set_foo(self.ptr, foo) }
}
impl Clone for Widget {
fn clone(&self) { let w = Widget { self.ptr }; w.ref_(); w }
}
impl Drop for Widget {
fn drop(&mut self) { self.unref() }
}
impl !Sync for Widget { } // See (II)
impl !Send for Widget { } // See (II)
What I'm not sure of is how to deal with borrowing here.
GTK gives us some *mut
pointers we can pass around that explicitly are aliased. It seemingly makes any kind of mutable (non-aliased) borrowing impossible or meaningless. For one the existence of clone
negates the point of having &mut
fns. Forbidding cloning seems unreasonable. Even if we managed to produce a unique pointer, so what, GTK wouldn't know or care.
So my instinct here is to make all fns take &self
. Is it correct? At least if we forbid Sync
and Send
?
II. Thread safety
GLib is completely thread safe (all global data is automatically locked), but individual data structure instances are not automatically locked for performance reasons. So e.g. you must coordinate accesses to the same GHashTable from multiple threads.
GTK+, however, is not thread safe. You should only use GTK+ and GDK from the thread gtk_init() and gtk_main() were called on. This is usually referred to as the “main thread”.
So the Widget case is relatively easy: we forbid Sync
and Send
and probably add a (!Send
) struct Gtk
to the wrapper like so:
struct Gtk {
_dummy: ()
}
impl Gtk {
pub fn new() -> Gtk { ffi::gtk_init(); Gtk { _dummy: () } }
pub fn main(&self) { ffi::gtk_main() }
}
impl Clone for Gtk {
fn clone(&self) -> Gtk { Gtk { _dummy: () } }
}
impl !Sync for Gtk { }
impl !Send for Gtk { }
struct Widget {
ptr: *mut GtkWidget,
gtk: Gtk,
}
impl Widget {
pub fn new(gtk: &Gtk) -> Widget { Widget { ptr: ffi::gtk_widget_new(), gtk: gtk.clone() } }
}
Does this make sense so far?
But what about GHashTable
? It could be made usable from different threads with some effort. Which std
types would help me wrap it correctly?