 Hello, welcome to Bridging World's Respite Scripts to Mix Existing Build Processes, I'm Gustavo Noranha, I'm with Colabra since 2009. Currently working a lot with mentorship and internships. I've been a free software developer from around 1999, I've been a dev and developer GNOME contributor, infamously creator of GKSUDU or GKSU, and for a while I was also one of the maintainers of WebKit GTK. So I've been trying Rust for a while. Two years ago I started learning, gave up, after fighting a lot with a borrow checker. Last year I decided to try it again and I've been in love with it since then. So I would like to go over a quick recap of what Rust is and how it can help us, what is the problem that we're trying to solve with the Rust build system and how Rust build scripts help us fix that problem. And I'll give you an example using G resource and a crate, which is a library in Rust speak that I built for it. So what is Rust? Why Rust and Cargo? This is what I would like to go over with a quick Rust recap. Rust is a systems language, that means it's an actual replacement for CNC++, it's compiled, it provides zero cost abstractions, it's very fast, it's very easy to interrupt with a C API, so you can use C libraries, existing infrastructure. It doesn't have runtime overhead like languages like Go for instance, which means it's also embedded friendly, you can even use it in boards or in systems that do not have a locator, so you can even use it for that. However, it's not like your usual C-like language in that if you're trying to learn Rust, just trying to pick it up as you would with a normal C-like language will most certainly not do. So you really need to come at Rust from a fresh perspective because it has several very different, very distinct concepts, especially in how it handles memory. And what that difference from C-like languages gives you is memory safety essentially. So Rust has a very powerful type system that is very flexible and very expressive and allows you to tell the compiler a lot of things that allows it to give you security that the code that you're writing is memory safe and that it behaves well even in multi-thread situations. So to give you a bit of an idea of what I mean when I say memory safety, I would like to go over this very interesting example that I stole from a talk on YouTube given by the creator of Rayon. Rayon is a library that allows you to very easily turn sequential workloads into parallel workloads. I highly recommend that you check it out. So what we're looking at in this example is a theoretical load images function. It receives an array of paths to images and it uses an iterator to go over each of these paths and load the images into image objects. So you can see that it returns a vector of image objects. At the same time, it also counts how many PNG files it saw while doing that loading of the images. So this is a fairly simple code and it will compile. Nothing relevant to see here. But when we introduce something like threads, this code may look maybe a bit more interesting. And that's what we did by replacing the iter there with parEter. ParEter is a simple feature of Rayon, this very powerful library that allows you to turn very easily sequential workloads into parallel workloads. So what Rayon will do here is using a thread pool, automatically break the array into several smaller pieces and run disclosure in parallel. So this code does not compile because there is no data arrays here. If you look at PNGs, the variable, it is now being used at the same time but by several instances of disclosure. So you have several threads fighting for changes, for reading and writing to this variable. So you may have a couple of threads, for instance, read the same value of PNG and when they add one to it, they will both write the same number to it. So that won't do right. You're going to end up with a miscalculation at the end. So how does Rust deal with this? So here's how Rust saves the day. This code simply does not compile. It knows that this closure will be executed in parallel by several threads. So it knows that one of them must own PNGs and it will tell you that you cannot touch this variable in another closure. So while the error message here could be better, it's not letting you commit an error that C, C++, Go, Python, any of the other languages that are out there would probably let you commit. So you would have a data race and not even know about it. So now we know how Rust did not allow us to build that code. How can we build that code? How can we let it build the code? How can we make it let us build the code again? One way to do that would be to use the atomic types that Rust provides. So it does provide a lot of atomic types, especially for the number types. So here we are using atomic use size and you can see that there's a pretty complicated method that you can call in it to add one to the value, right? So this will make sure that atomicity is ensured. You can choose the ordering in which that happens and this code will now compile again. Another way to fix this problem is to use something that is more likely to be found in other languages like a mutex. So I think that it's an interesting example to give as well because it shows how mutexes are very different in terms of how they work in Rust compared to other languages. Usually in other languages you have a mutex where you lock a specific part of the code, right? So you have a specific block of code that is locked by that mutex and that is kind of true as well for Rust but Rust also knows about the data that it is protecting and you can see that it returns a variable that's called a guard. So the type that it returns when you lock it is not the data itself, it's a guard. So it works more or less like C++ RAII concept in which the guard, while the guard lives, you can still use the data, right? It's still locked. You can drop the guard or when it goes out of scope it will be automatically dropped so there's no forgetting to unlock a mutex for instance. So that's very good. Now let's talk a little bit about cargo. Cargo is the Rust build system. It's not the Rust compiler but it's its build system and it's also kind of a state-of-the-art package manager so you can use it to install Rust tools like BAT which is a kept replacement interior system. Cargo is, as we will see in the next slides, very focused on Rust code itself. So what is a problem with cargo? Cargo is made to build Rust binaries essentially or libraries and that's it, right? It doesn't really provide all the support for things like data files, creating translations, etc. that other build systems provide, such as Amazon or Cmake, that kind of thing. So you really don't have a lot of configuration of ways of configuring a cargo project to build anything other than the application code itself, right? The binary itself. But you need that in modern applications. If you take a GTK GUI application for instance, it will have XML files that describe the UI. It will have translations that need to be included in the installation and those translations are actually messages that are sucked out of the code base including all of these XML files put into files that are then used by translators to translate and then those files are compiled during the build of the application and need to be installed and loaded by that application. So there's a lot involved here that cargo does not help us with at the moment. And that's why, for instance, if you see a GTK application, how it's supposed to be built with Rust these days, it's still by using Meson which integrates with cargo underneath. Now I just said that Rust does not give you a lot of control in terms of configuration but contradicting myself here, Rust does give you a lot of flexibility in how the build works because you can use something called a build script. And that build script is literally a build.rs source file that you put into the root of your code base and it gets built and it's executed during the building of the code base to help you do anything special that you need to do. So for instance, whenever people are using something like proto buffers, they will use build.rs to take the definitions and generate the code with it. This is an example of a build script that I am playing with. It provides me a way of giving the build a prefix so that the application knows at runtime where it's supposed to be installed so that it can look for, for instance, translation files by using an environment variable during the build process. So I set duplicate prefix as an environment variable and I add this build script to my code base. It will read the environment variable and it will generate a Rust source file that includes a static global variable that the rest of the code can use. If you look at the end of this example, there's a print line there which has a very specific format and what that string is saying is you need to rebuild if this environment variable changes. So there's a lot of control that you can have here to specify if this file changes I need you to rebuild if this environment variable changes I need you to rebuild. So let's stop talking about Rust itself for a second and talk about something called gresource. gresource is a very neat functionality provided by GNOME's GIO library and what it does is it gives applications a very simple and powerful interface to load data files from various sources. So that can be out of the file system or, and I think that this is the most interesting way of using gresource, is it gives you a way of embedding data files into your binary so that it becomes a lot more portable and a lot less reliant on where it's installed and all the paths etc. And the way it does that is you write an XML description of the files that you want to embed and during the compilation during the building of your application it will run this glib compile resources binary to generate a C file and that C file is going to be built and linked into your application. A C file you say, so how well does that rule, how do we do that in Rust, right? So thankfully there is an existing crate called CC which can be used in build scripts to essentially do exactly that. So you take a C file and you build it into your Rust application. Like I said before Rust does interface very neatly with CABIs so if there is a CABI you can even build C++ files into your Rust application. And we won't go into too much detail here but what's usually done is you then wrap those CABIs, CABI calls into unsafe blocks providing safe interfaces in Rust for the rest of the Rust code to use. You don't want all of your Rust code to be calling unsafe CABI functions. Okay so the CC crate would already be a very big help for us here but you still need to do all of the other work, right? You need to read the XML file, you need to figure out what are the source files it refers to so that you can let cargo know you need to rebuild when these files changes change. You need to build to call JLid build compile resources to generate that C file and then you use the CC crate to build that C file into your code base. So what I did was I put all of that work I just said into a nice crate for us to use called Jbuild and you just need to tell it where to find the source code, what's the XML file and it will do all of the work for you. So here's an example of the Rust code that I used then in the application to load that file that got compiled into the binary. As you can see I don't need to provide anything, I don't even need to call any C functions here because GIO's C code that got generated is so nice that it will register constructors, process constructors that will automatically register all of the data into the GIO infrastructure. So all I need to do is really call a load from resource and several GTK and GIO objects provide that functionality like GIO's file and in this case here GTK's CSS provider. So I'm loading the CSS file that I used to style my widgets from the binary itself using G-Resource. Okay so there's a lot to talk about in terms of build scripts and Rust and G-Resource and GTK but I'll leave you here. I hope you have some questions for us to discuss. I'll be available of course to answer them if I haven't already during the talk. Just a final message, we are always hiring if some of this looks interesting to you, if you're looking into kernel, graphics, multimedia work, G-streamer related etc. Give us a call and thank you.