(First part of the series, with index to all the articles)
In the first part, we saw how glib-rs provides
the FromGlib
and ToGlib
traits to let Rust
code convert from/to Glib's simple types, like to convert from a Glib
gboolean
to a Rust bool
and vice-versa. We also saw the special
needs of strings; since they are passed by reference and are not
copied as simple values, we can use
FromGlibPtrNone
and
FromGlibPtrFull
depending on what kind of
ownership transfer we want, none for "just make it look like we are
using a borrowed reference", or full for "I'll take over the data and
free it when I'm done". Going the other way around, we can use
ToGlibPtr
and its methods to pass things from Rust to
Glib.
In this part, we'll see the tools that glib-rs provides to do conversions of more complex data types. We'll look at two cases:
-
Passing null-terminated arrays of strings from Glib to Rust
And one final case just in passing:
Passing arrays from Glib to Rust
We'll look at the case for transferring null-terminated arrays of strings, since it's an interesting one. There are other traits to convert from Glib arrays whose length is known, not implied with a NULL element, but for now we'll only look at arrays of strings.
Null-terminated arrays of strings
Look at this function for GtkAboutDialog
:
/**
* gtk_about_dialog_add_credit_section:
* @about: A #GtkAboutDialog
* @section_name: The name of the section
* @people: (array zero-terminated=1): The people who belong to that section
* ...
*/
void
gtk_about_dialog_add_credit_section (GtkAboutDialog *about,
const gchar *section_name,
const gchar **people)
You would use this like
const gchar *translators[] = {
"Alice <alice@example.com>",
"Bob <bob@example.com>",
"Clara <clara@example.com>",
NULL
};
gtk_about_dialog_add_credit_section (my_about_dialog, _("Translators"), translators);
The function expects an array of gchar *
, where the last element is
a NULL. Instead of passing an explicit length for the array, it's
done implicitly by requiring a NULL pointer after the last element.
The gtk-doc annotation says (array zero-terminated=1)
. When we
generate information for the GObject-Introspection Repository (GIR),
this is what comes out:
1 2 3 4 5 6 7 8 9 10 |
|
You can see the transfer-ownership="none"
in line 5. This means
that the function will not take ownership of the passed array; it will
make its own copy instead. By convention, GIR assumes that arrays of
strings are NULL-terminated, so there is no special annotation for
that here. If we were implementing this function in Rust, how would we
read that C array of UTF-8 strings and turn it into a Rust
Vec<String>
or something? Easy:
let c_char_array: *mut *mut c_char = ...; // comes from Glib
let rust_translators = FromGlibPtrContainer::from_glib_none(c_char_array);
// rust_translators is a Vec<String>
Let's look at how this bad boy is implemented.
First stage: impl FromGlibPtrContainer for Vec<T>
We want to go from a "*mut *mut c_char
" (in C parlance, a "gchar **
")
to a Vec<String>
. Indeed, there is an implementation of the
FromGlibPtrContainer
trait for Vec
s
here. These are the first few lines:
impl <P: Ptr, PP: Ptr, T: FromGlibPtrArrayContainerAsVec<P, PP>> FromGlibPtrContainer<P, PP> for Vec<T> {
unsafe fn from_glib_none(ptr: PP) -> Vec<T> {
FromGlibPtrArrayContainerAsVec::from_glib_none_as_vec(ptr)
}
So... that from_glib_none()
will return a Vec<T>
, which is what we
want. Let's look at the first few lines of FromGlibPtrArrayContainerAsVec
:
1 2 3 4 |
|
Aha! This is inside a macro, thus the $ffi_name
garbage.
It's done like that so the same trait can be implemented for const
and
mut
pointers to c_char
.
See the call to c_ptr_array_len()
in line 3? That's what figures
out where the NULL pointer is at the end of the array: it figures out
the array's length.
Second stage: impl FromGlibContainerAsVec::from_glib_none_num_as_vec()
Now that the length of the array is known, the implementation calls
FromGlibContainerAsVec::from_glib_none_num_as_vec()
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Lines 3/4: If the number of elements is zero, or the array is NULL,
return an empty Vec
.
Line 7: Allocate a Vec
of suitable size.
Lines 8/9: For each of the pointers in the C array, call
from_glib_none()
to convert it from a *const c_char
to a String
,
like we saw in the first part.
Done! We started with a *mut *mut c_char
or a *const *const
c_char
and ended up with a Vec<String>
, which is what we wanted.
Passing GList
s to Rust
Some functions don't give you an array; they give you a GList
or
GSList
. There is an implementation of
FromGlibPtrArrayContainerAsVec
that understands
GList
:
impl<T> FromGlibPtrArrayContainerAsVec<<T as GlibPtrDefault>::GlibType, *mut glib_ffi::GList> for T
where T: GlibPtrDefault + FromGlibPtrNone<<T as GlibPtrDefault>::GlibType> + FromGlibPtrFull<<T as GlibPtrDefault>::GlibType> {
unsafe fn from_glib_none_as_vec(ptr: *mut glib_ffi::GList) -> Vec<T> {
let num = glib_ffi::g_list_length(ptr) as usize;
FromGlibContainer::from_glib_none_num(ptr, num)
}
The impl
declaration is pretty horrible, so just look at the
method: from_glib_none_as_vec()
takes in a GList
, then calls
g_list_length()
on it, and finally calls
FromGlibContainer::from_glib_none_num()
with the length it computed.
I have a Glib container and its length
In turn, that from_glib_none_num()
goes here:
impl <P, PP: Ptr, T: FromGlibContainerAsVec<P, PP>> FromGlibContainer<P, PP> for Vec<T> {
unsafe fn from_glib_none_num(ptr: PP, num: usize) -> Vec<T> {
FromGlibContainerAsVec::from_glib_none_num_as_vec(ptr, num)
}
Okay, getting closer to the actual implementation.
Give me a vector already
Finally, we get to the function that walks the GList
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Again, ignore the horrible impl
declaration and just look at
from_glib_none_num_as_vec()
.
Line 4: that function takes in a ptr
to a GList
, and a num
with
the list's length, which we already computed above.
Line 5: Return an empty vector if we have an empty list.
Line 8: Allocate a vector of suitable capacity.
Line 9: For each element, convert it with from_glib_none()
and push
it to the array.
Line 14: Walk to the next element in the list.
Passing containers from Rust to Glib
This post is getting a bit long, so I'll just mention this briefly.
There is a trait ToGlibContainerFromSlice
that takes a Rust slice,
and can convert it to various Glib types.
-
To
GSlist
andGList
. These have methods liketo_glib_none_from_slice()
andto_glib_full_from_slice()
-
To an array of fundamental types. Here, you can choose between
to_glib_none_from_slice()
, which gives you aStash
like we saw the last time. Or, you can useto_glib_full_from_slice()
, which gives you back ag_malloc()
ed array with copied items. Finally,to_glib_container_from_slice()
gives you back ag_malloc()
ed array of pointers to values rather than plain values themselves. Which function you choose depends on which C API you want to call.
I hope this post gives you enough practice to be able to "follow the traits" for each of those if you want to look at the implementations.
Next up
Passing boxed types, like public structs.
Passing reference-counted types.
How glib-rs wraps GObjects.