In this blog, I’m going to post about the progress I make and the things that strike me during my Outreachy internship. Outreachy interns work on an open-source project for three months under the guidance of a mentor from the community. In my case the community is GNOME, my mentor is danigm, and the project is Fractal. Fractal is a pretty cool gtk-desktop application for real-time communication through the Matrix protocol. It’s written in Rust. Here’s a link: https://wiki.gnome.org/Apps/Fractal
The goal of my internship is to implement a video player in Fractal. Right now receiving a message of a video attachment is handled the same way as receiving a pdf attachment: the functionalities Fractal provides for it are “open” (with another application) and “save”.
I’m going to integrate a video player into the Fractal application that allows the user to directly play the video inside the application. I’ll use GStreamer for that.
About the programming language: Rust
In order to ask for an Outreachy grant for a certain open-source project, applicants first have to contribute to that project for about a month. When choosing a project, I didn’t know any Rust. But the fact that Fractal is written in Rust was an important point in favor due to curiosity. But I also expected to have a hard time at the beginning. Fortunately, that wasn’t really the case. For those who haven’t used Rust, let me give two of the reasons why:
If you just start coding, the compiler takes you by the hand giving you advice like “You have done X. You can’t do that because of Y. Did you maybe mean to do Z?”. I took those pieces of advice as an opportunity to dig into the rules I had violated. That’s definitely a possible way to get a first grip on Rust.
Nevertheless, there are pretty good sources to learn the basics, for example, the Rust Book. Well, to be precise, there’s at least one (sorry, I’m a mathematician, can’t help it, I’ve only started reading that one so far). It’s not short, but it’s very fast to read and easy to understand. In my opinion, the only exception being the topics on lifetimes. But lifetimes can still be understood by other means.
About the GUI library: GTK-rs
The GUI library Fractal uses is GTK-rs, a wrapper in Rust around the C-library GTK. One random interesting fact about GTK-rs, that called my attention at some point reading the Fractal code, was the following. Based on GObject, GTK uses inheritance structures. For example, the class
Label is a subclass of
Widget. In Rust there aren’t classes.
Label is a type and
Widget is so as well. So how does
Label inherit from
Widget in GTK-rs? Well, strictly speaking, it doesn’t. But both types implement a trait called
Cast (in Haskell jargon: they are of type class Cast). In fact, any type in GTK-rs coming from GObject implements
Cast trait allows a type to be converted to types corresponding to superclasses (or subclasses, when that makes sense) in the GObject tree. That’s how you can convert a
label to a
widget, call a
Widget method on it, and -if you want- convert it back.
Converting an instance of a type to a type corresponding to a super- or subclass (in the GObject logic) is called upcast or downcast, respectively. But how does GTK-rs capture the subclass/superclass logic, if the concept of classes doesn’t exist? The answer is: via the trait
T is a type parameter). If a type corresponds to a GObject subclass of another type
P, then it implements the trait
P is a concrete type: the one corresponding to the superclass). For example,
Label implements the trait
IsA<Widget>. Of course,
Widget has far more subclasses than just
Label. All of them implement the trait
Now, let me come back to the end of the penultimate paragraph and explain why the
Cast trait allows a
label to be upcasted to a
widget. By definition of the trait
Cast, saying that
Cast means that it has to have one method
upcast<P> for every type
P for which it implements
IsA<P>. So it has to have a method
upcast<Widget>. That method converts a
label into a
Downcasting methods are guaranteed very similarly. To start with, whenever a type
T implements the trait
IsA<P> for some type
P corresponds to a GObject superclass of
T), the type
P implements the trait
CanDowncast<Label>. Again by definition of the trait
Cast means that it has to have one method
downcast<T> for every type
T for which it implements
CanDowncast<T>. So it has to have a method
downcast<Label>. Notice that a
widget can only be downcasted to a
label, if it comes from a
label in the first place. That is captured by the fact that the return type of
Of course, if it wasn’t for wrapping around an object- and inheritance-oriented library, one might directly work in a different mindset in Rust. But it’s interesting to see the tricks that have been used to realize this GObject mindset in Rust.