Type conversions between Rust and Glib
Conversions in methods
Consider a method like this:
# #![allow(unused_variables)] #fn main() { class Foo { } impl Foo { virtual pub fn my_method(&self, an_int: u32, a_string: &str) -> bool; } #}
If this were C code, we would be using a prototype like this:
gboolean my_method(Foo *foo, guint an_int, const char *a_string);
Here, the Rust types are not the same as the C-side Glib types:
bool
/gboolean
u32
/guint
&str
/char *
These conversions of values can be done with the ToGlib
and
FromGlib
family of traits in glib-rs. However, we need to convert
the types as well, so that we can generate trampolines.
Extern functions for methods
What does a virtual method look like in GObject? It is a function pointer inside a class structure. The method above would be something like
struct FooClass {
GObjectClass parent_class;
gboolean (* my_method)(Foo *foo, guint an_int, const char *a_string);
}
By convention, C code implements a public function that calls this virtual method by dereferencing the function pointer:
gboolean
foo_my_method (Foo *foo, guint an_int, const char *a_string)
{
FooClass *klass = FOO_GET_CLASS(foo);
(* klass->my_method) (foo, an_int, a_string);
}
This function does the following:
-
Given a
foo
instance, find its class structure. -
Dereference the
klass->my_method
function pointer and call into it.
Language bindings expect this public function to be present: they call
into it so that the function can do its own argument checking and so
on. The gnome-class code generator must generate an ABI-compatible
function as a pub unsafe extern "C" fn
. We do this in
imp::extern_methods
:
#[no_mangle]
pub unsafe extern "C" fn #ffi_name(this: *mut #InstanceNameFfi,
#inputs)
-> #output
{
#callback_guard
let klass = (*this).get_class();
// We unwrap() because klass.method_name is always set to a method_trampoline
(klass.#name.as_ref().unwrap())(this, #args)
}
Note that this function: a) takes a raw pointer to an FFI struct for
the instance on which the method is being called; b) calls a function
pointer inside the klass
vtable, with C types for arguments. In
effect, this is as if we had written a C function that just calls the
function pointer inside the vtable.
Trampolines
So far, we have a Rust method function callable from C, that calls a function pointer with C types. We need to do a few things to glue this nicely to Rust code:
-
Go from the
this
(a raw pointer to an FFI instance structure) in the function above, to a Rust&self
. -
Convert C types from arguments into Rust types, with glib-rs.
This is what a trampoline does: it converts the arguments and
obtains the &self
. We generate trampolines in
imp::instance_slot_trampolines
:
Argument conversions
Given a function signature like
virtual pub fn my_method(&self, an_int: u32, a_string: &str) -> bool;
we need to generate a few things:
-
The return type as a Glib type, i.e.
bool
gets translated togboolean
. -
Input arguments but with Glib types, i.e.
an_int: u32, a_string: &str
gets translated toan_int: u32, a_string: *const libc::c_char
. -
Just the list of Rust types, i.e.
u32, &str
for use in aFn
closure declaration:Fn(&Self, u32, &str)
that doesn't have argument names. -
Each argument value converted from a Rust type to a Glib type:
<u32 as ToGlib>::to_glib(&an_int), <&str as ToGlibPtr>::to_glib_none(a_string)
. -
Each argument value converted from a Glib type to a Rust type:
<u32 as FromGlib<_>>::from_glib(an_int), <&str as FromGlibPtrBorrow<_>>::from_glib_borrow(a_string)
. -
etc.
Our representation of a method or signal signature is hir::FnSig
.
It provides methods like output_glib_type()
or
input_args_to_glib_types()
that generate the conversions above.
This is done by wrapping the FnSig
's fields into helper types, and
then those helper types have impl ToTokens
; those implementations
generate the appropriate code.