 Hi everybody, my name is Jonathan Creekmore. I'm here to talk to you about Rust today. So, kind of just a general overview. First I'm going to explain kind of why, what I believe system programming to be and how Rust helps solve what I see as some of the problems with systems programming languages. Then I'm going to give just a really quick overview of Rust. This is not going to be like a tutorial or an in-depth discussion on it, just hitting the high points and showing kind of how it will work around some of the common issues that C or C++ may have. I'm going to go into briefly how you can build Rust programs, how you can build them with Yachto, and then some of the kind of rough edges that Rust itself has. Alright, so who am I? So I've been doing systems programming for 15-some-odd years. I've been programming in general for like 2025 and spent a good portion of my career doing a small embedded RTOS and driver support for routers and switches. Moved into a little bit of Linux kernel development and some security software. Bounced into an IoT company and now back doing hypervisor work and more security software. I like anything programming theory related, math, history, hiking, just all sorts of different things. But I spend most of my time writing software and whenever I can, I write it in Rust these days. Alright, so what is systems programming? What do I consider systems programming to be? Well, comparing that to applications programming, systems programs provide services to other programs. They're not something that the user is really going to be executing themselves, but are rather in support of those user programs. So, for instance, the Linux kernel would be a systems program. Any OS kernel would be, a driver. Glib C would be a systems program. So would the JVM. So any type of language runtime would be a systems program. So like I said, any type of program that's not the main reason a user is sitting down at the computer and trying to get useful work done would be a systems program of some sort. You typically would see them in some place where your resources are very constrained either because they're not doing what the user is really looking to have done. They're just that base-level software, or you're dealing with an embedded system where you have a very small set of memory, a very constrained CPU. Like I said, they're used to build abstractions for applications programs, so libraries, language runtimes, OS kernels, that type of thing. What I see the sharp edges of systems programming mainly to be is the manual memory management that you wind up having to do. So applications programming languages generally provide garbage collectors so that the user does not have to deal with allocating memory or releasing it at the right time or remembering what memory is in use and what memory can be reused. But the garbage collector does incur some amount of overhead in your program. You have memory overhead of it's going to allocate more memory than you necessarily need. It's going to hold on to it longer than the system's necessarily going to want to hold on to it. Periodically, you're going to have pauses when the garbage collector has to run and go through and sweep and find memory that it can free up. So if you have very resource constrained applications, you may not want to incur that overhead of a garbage collector. Another issue is if you have library code, it's hard to write that in a high level language because especially if you have a runtime associated with that language, because how do you share that among other languages? So how can I as a C application writer use something that is a Java library? I pretty much just can't. So why is manual memory management challenging? So a lot of the reasons why it's challenging is because the programmer just has to keep up with a lot of extra stuff and always make sure that they get it right. So the programmer has to keep track of the scope of all the memory allocated in the system. Where were data objects allocated? When do they get freed? Who's using them and have they stopped using them before they got freed? If the programmer is not careful, they will cause memory leaks where of course the programmer forgot to free memory after they were done with it and it can't be reclaimed then and now the system doesn't have access to it until the program exits. Or the programmer may have dangling pointers left over where they freed something but they still have pointers that are pointing to that memory and if they accidentally try to use it, then they're going to cause corruption. You also wind up having other issues associated with it of managing the amount of memory that you need for buffers and that type of thing so that you might have buffer overflows and walking across other memory that's allocated in the system. Programmers also have to make sure that their code doesn't have data races. As just a real easy example, not talking about threading, if you're walking a link list and trying to remove elements from that list while you're walking it, if you're not extremely careful in how you're doing that, you may wind up having a pointer that's then been freed and now you're trying to use that memory to get to the next part of the list. None of these problems are real showstoppers in a typical systems programming language. A careful programmer can manage all of those issues. I mean obviously we can, we've been using C for 40 some odd years and we're very successful using C but a lot of care must be taken and as a programmer I don't know about you, I'm a little lazy. I don't want to have to keep track of all these details. That's what I have a computer for. I have a computer to make my life easier. So I want to give just kind of a real speedy tour through Rust. I'm going to show some examples of some issues in C or C++ and then how you would write the same type of code in Rust so that the problem doesn't exist. But first off, Rust just gives you kind of a helping hand. It has a strong static typing system that allows the type system to give you several safety guarantees at compile time. Rust encodes ownership and lifetimes of data objects directly into the type system. So the compiler assists in keeping track of how long your data lives. But just like you wouldn't see when you do stack allocated memory, the compiler is more or less keeping track of that. Rust extends that into dynamically managed chunks of memory and tracks the lifetime as it's being used and passes the memory from variable to variable and then when it detects that it's no longer used it frees it up. Rust also treats all data as immutable by default. Immutability is provided by other languages through const, but that's opt-in. Rust flips that on its head and says, well, you know, instead of opting into immutability, we're going to make it opt-out. That just encourages you to reduce the mutation that you're doing in your program and makes it a little bit easier to reason about functions and helps prevent those data-race problems that I mentioned a little bit earlier. Plus, ownership is transferred by default. So in C or C++, you're actually doing copies a lot of times, or always. I mean, every value is passed by copy into functions and out of functions and all that. If you want things to be passed by reference, you have to give a pointer. In Rust, you're always moving the data from scope to scope. If you don't want to move data around, then you borrow data rather than transferring it. And I'll show a little bit of an example of that in just a few minutes. Okay, so this is just a real simple memory leak. Take a look at it and see if you can see where the memory leak is. I think there's actually probably a couple of small little issues with this program, but I wrote it quickly. So like I said, there's a little memory leak in it. In our test function, we're using sturdup to allocate that string, assigning it to test. We pass it into a function. That function does something with the memory, allocates its own memory, returns something out, finds it back to test, and now we've lost track of that originally allocated memory. Without very well-documented functions in C, we have no way of knowing, short of reading all the code, what's going to happen to the parameters that are passed in. That's something that we have to, as programmers, document. In Rust, we wind up not having to do that. The compiler tracks it for you. But the real issue here is who owns the memory that was passed in? Nothing in the code specifies it. Nothing in the code says, oh, well, the test function owns it, so it's responsible for cleaning everything up, or did we pass it into that upcase function, and it's responsible for cleaning it up? Like I said, we can only track that by convention through documentation. So in Rust, I've got two different methods up here, two different types of upcase methods, and the first one does explicit ownership transfer. So in this case, input takes a string, and like I said, in Rust, by default, you're passing things in. You're doing a move operation on it. So this says that that upcase function, when it's called, will own the string that gets passed into it. What that means is that the calling function will no longer be able to use the variable that passed that in. So there's no ambiguity about who owns that memory at that point. The compiler can then free it up when the function is done in this case. But what if you don't want that to happen? Well, in that case, Rust has borrowing, where you pass in by reference, that's what the little ampersand means, and that means that the caller still owns that memory, and the called function is only borrowing access to that memory. So it's very explicit exactly how that is used. Okay, a very similar thing in C++. I don't know if there are any C++ programmers here, but it's a little subtly different. So in this case, I'm allocating this big object, and big object has something else inside of it, some object bar. And then I call a function on it that consumes the foo object. So I am taking that in, and it's gonna delete foo after it's done with it. Like you see, foo is now null, I can't use it, but I grabbed a reference to bar. So I'm still holding on to a reference, and that memory is now no longer valid. It could have been allocated by something else, it could have been cleaned up by the delete. I have no way of knowing. When I call do it on that object, it's undefined what's gonna happen. So how would Rust deal with this? Well, in this case, we have very similar code, so consume is going to take in big object, and it's not gonna do anything with it, but since it's passed in by ownership, it's gonna free it when it's done, so I don't really care what consume does in this case. Doing the same type of thing, I can allocate one of those objects, I can get a reference to it, I call consume, I call do it. And that's a compiler error. What winds up happening is that the reference to that bar method is called a borrow, so we're borrowing access to it, and then we won't be allowed to move into that consume function, because we have a reference. So the compiler helps us out and prevents us from making a mistake. What we could have done by reference to consume, but that would not have freed up that memory. So a little subtle issue that's bitten me in C++ in the past is using the STL. So the STL gives you a lot of data structures that can be used, and in this case I'm using a deck, and I'm wanting to walk through that deck and erase anything that's in it that's even. So all of my even variables, I want to erase those out. Well, the STL has a very subtle issue of if I happen to mutate, if I delete an iterator while I'm using it, all other iterators to that data structure are invalid. So if I do this erase, the iterator IT that I'm using, I then cannot be guaranteed to increment pass that and go to the next version. If I try to do the same type of thing in Rust, I get another compiler error. So this is another one of those data races that I was saying. In this case, I am doing an immutable borrow of the input data structure. So I have a reference to it. I'm wanting to mutably do something to that. Remove is mutating the input structure. That's not allowed because I have an immutable reference to it. So once again, Rust is just helping protect me from writing code that can be racy. Not necessarily saying that it would be a problem in production, but it could be. Generally, you wind up having to restructure your code a little bit to work around issues like that. Since this is a compiler error, you would have to rewrite this database differently. It could be a little bit of a pain, but it makes your code more robust. Okay, so that's just little bits of things of how Rust is protecting you in problematic areas. What are some of the exciting features that I really like about Rust? Sorry. Okay, so algebraic data types. I love good algebraic data types. So this is something that you get enhancements to things that you would normally see in C or C++. So a sum type would be some sort of an enumeration, but it's really more like a variant. So you think of a C enum, and it's just saying it's one of these set of values. Well, in Rust, their enum type, you can also associate data with that value. So in this case, the enum sum has a type value of foo. It has bar, which also takes two arguments, and baz takes two named arguments. So if I'm trying to access... Think of it in C as a struct with an enum in it, and then a union that has different values in it, depending on which value the enum has. With Rust, it enforces that, say, that u-size and string value is only there when bar is the value of the enum. If I'm using foo, I can't access those variables because they just don't exist. The product type is just like your normal structs in C or C++. You also wind up having a tuple type that is just unnamed variables. You don't have named fields. The way you work with a lot of that is through pattern matching. Pattern matching is very powerful in Rust. So it works similar to a switch statement in C or C++. In this case, I have that enum just like I had defined before. I can do a match, like I said, similar to a switch on the variable that I have in there, and I have a set of cases. In this case, I have, well, if it's foo, I don't know what to do, so I'm just going to return zero because that's the only possible value. There's no value associated with foo. If I have bar, well, I have a u-size that I can use, so let me just return that u-size. I can destructure the enum and peel out the different pieces that are associated with that value. And with Baz, I can do the same type of thing. So that match statement destructures your data type and gives you access to all those interior fields, and it does it by borrowing. I don't have a specific slide about this, but if you'll notice here, I have let foo equals some value. I did not declare a data type anywhere in here. Rust does type inference so that you don't have to necessarily specify what types all your variables are. The compiler will figure it out for you and keep track of all those static types. In some cases, it does get a little confused, and you have to specify this is what my type is, but in general, the compiler's really good about figuring it out. So in this case, foo, it figures out is that type pubsum, and it figures out that value is a type usize, because that's how it's always being used. If you tried to do one of those returning a string and one of those turning a usize, it would error at compile time because you're not being correct with your types. Another one of my favorite features are generics and traits, so if you're familiar with C++, think templates are the equivalent of generics. So what they allow you to do is write the code for an algorithm once and then apply it to many different data types in a thread safe manner. So here I wrote a simple one called print truthy, and it takes the value that was passed in and calls is truthy on it and prints that out for you. One of the things that it's also saying is hey, that value is of type t, and type t has to either be debuggable or has to be debuggable and truthiness. Truthiness is a trait. Traits are almost like an interface if you're familiar with Java or I guess an abstract-based class for C++. They let you define an interface that can be called on a data type. You can define your trait at any point. In this case, I define this truthiness trait, and it has a function called as truthy. Then I was able to implement that trait for built-in data types or any other data type in the system. So long as I'm defining the trait, I can implement it on any data type. If I am using somebody else's trait, I can implement it on my own data types. Traits give power to generic functions because they let the compiler know what operations are supported by that data type. So in C++, they have a concept called concepts. Is that right? I think so. That have not been standardized yet, and they do something very similar so that the compiler has an idea of what operations are allowed on these types. Okay. Another place where Rust helps out is in kind of traditional error handling. So in C++, you can return null pointers, and Tony Hoare called null pointers the billion-dollar mistake because there are so many issues with returning null and accidentally dereferencing these null pointers. Additionally, a lot of functions in C or C++ overload the return type of the function with error handling. So you might have, say, positive numbers are valid, but then negative numbers are error cases. And then you have to make sure that you're checking, oh, is this positive or negative before I'm using it? And there's a lot of things you just have to know by convention. Well, Rust tries to correct that since we have that strong type system. One of the first things that they have is the option type. So that gives you the ability to represent I returned an object or I didn't have one. So I have some object, some type T, or I have nine. So how is that different than I've returned an object or I've returned a null pointer? Well, in this case, the type system forces you to handle that error case. So if you're getting back an object and it's an option type, in general, you have to match on that option type to get the value out. And if you're matching on that option type to get the value out, you also have to handle the other case, which is the none case. So you have to deal with the error case. Now, I want to put a little asterisk on that because you don't have to. Rust has something called unwrap. Unwrap says, yeah, that's good. Just give me what's supposed to be in there and blow up and panic if it's none. Usually you would only do that in development cases. I don't recommend doing it if you can avoid it. But in general, you have the ability to easily track those error cases. The option type also gives you the ability to chain methods together. So in this case, I have some value 42, and then I can map a function across that value and get back another option type with the result. So in this case, the x plus eight, that's an anonymous function. It's applied to the value that is inside that sum and turns that into 50, and it wraps it back up in the sum and pops it out. The very bottom line, the none I'm applying the same function to, well, in that case, none doesn't have a value, so it just passes the none along. So you can chain several operations together, and if any of them fail, then you'll get the failure case out. You don't have to do a bunch of explicit error checks. And you also have the option to do an and then, which is in the case where the function you're wanting to pass back returns an option itself instead of just a plain function. If you need more data than just this failed, you have a result type. Result gives you okay and error. So that gives you the, say for instance, like the error no. So I need more data that's coming back. I have a good value, and I have some error value that needs to come back. It can be a string, it can be an enum of some sort, whatever you choose. The type system, once again, forces handling of all those error cases. Same types of chaining methods are available. They also have a map error to let you change the type of that error type. So if you get back an error no, you could map it to a string if you wanted to. And both option and result have ways to go back and forth between each other. So some of the other features in brief, we wouldn't be a systems programming language if we did not have some way of writing unsafe code. I mean, there's some things you just, you can't be completely safe about. If you're trying to do pointer manipulation because I'm writing an operating system, or I'm interfacing with an existing C library, I have a way of saying this set of functions, this set of code even is unsafe, and so I can break some of these safety features that I talked about earlier, like the data race issues and doing all the lifetime checks. So some of that gets broken by wrapping things in unsafe. Generally, if I'm wanting to do a foreign function interface, so I have an existing C library, I'm gonna say open SSL, even though there's already a wrapper, but if I was wanting to wrap a C library, I would make a little set of functions that is unsafe, and then I would wrap it in a set of safe functions in Rust to expose that to the rest of my code. Other useful things are hygienic macros. So these are not your macros like you would have as a C poundifying, which is just a simple text substitution. The Rust macros, you get the syntax tree passed to you, and you can do operations on the syntax tree to generate code. And new in, I believe, Rust 115, they added something called procedure macros to help you automatically derive certain instances of code for data structures. That's very powerful. Okay, so building applications in Rust. Rust has a normal compiler called Rust-C, and you can call out to Rust-C and build your code that way. And almost nobody does it. Instead, Rust has something called cargo. Cargo is a build tool and dependency manager for Rust. So if you're familiar with JavaScript and you have something like NPM that fetches down your packages, builds them up for you and gets them ready to be used, cargo does something very similar for Rust. It builds packages called crates. It manages the complete dependency graph of your program. So it makes sure that the packages that you pull down are the versions that you're looking for and that they don't conflict with each other. Cargo also has test integration built in. So there's a command cargo test that will automatically walk through all your source code, pull out things that are wrapped with the test feature flag, run unit tests on, compile those for unit tests and run them and then collect the test results. It also can do integration testing for inner compilation unit functions that are testing the integration of different modules in your code. One of my favorite things is doc tests. So Rust has a documentation syntax very similar to Doxygen, but you can put example code inside your tests and when you run cargo tests, it extracts all the tests in those comments and runs those as well. So the code comments and the documentation that you're generating will always be valid if you're running your test suite because those examples are compiled against your existing program and run and verified to make sure that everything still works. Cargo ties into crates.io which is the community crate host. You can also have crates that are tied in through like GitHub or just Git in general so it'll fetch down just directly from Git. It'll fetch from other locations in your file system. The cargo documentation is pretty good. It has a good getting started guide. It's worth reading through to make sure that it can do. So MetaRust also has support... Yachto also has support for Rust. So the company I work at, we use MetaRust and two of our engineers are maintainers of MetaRust and we use it to build several of our user space utilities in Yachto. Currently it has... depending on which release you're on, try to keep support for two Rust versions, the legacy version and then the default version for that release. So with Krogoth, if you started with Krogoth, your Rust version would be 1.12.1. If you then upgraded to Morty, you're still gonna be pinned on 1.12.1, but then you have the opportunity to move yourself up to Rust 1.14. They're trying to do two versions because Rust releases every six weeks or so, a new release of the compiler. So we try to keep as current as we can. For Pyro, we're expecting to get up to 1.16, but we may skip and go straight to 1.17 in that case. So another one of my coworkers wrote a command called cargo bitbake. So since Rust has its own build system and its own way of specifying what all of your dependencies are and how your application gets built up, he wrote cargo bitbake, which is just a little tool to parse that cargo file and auto-generate the bitbake file so that it will pull down all the dependencies while you still have network and then let Yachto go off and build without network. So the target bitbake file uses the meta-Rust create feature to download all of its dependencies and then cargo can build that up inside the normal build process. Okay, Rust is not all golden. It does have a few rough edges. One of the ones that you'll see if you do any research on Rust and start playing with it, and especially if you start writing Rust is what's called fighting the borrower checker. There's one before where you pass ownership by default but you're borrowing a lot of the time because you don't necessarily want to pass variables around all the time, and it can take a little while to wrap your head around all the ownership rules of Rust. Eventually it does click, but until it clicks, you're gonna find a lot of borrow checker errors. Luckily the compiler in Rust is getting better and better, and when you start seeing these problems, it now will give you, in the error message, here's your code, you saw earlier that it said this function, this line is borrowing, this line can't let it happen, here's what it is. So it gives you a lot of good explanation to how to fix the problems in your code. Back when I started doing Rust in 1.1, I think it was a lot harder. The error messages weren't nearly as good, but with starting with about 1.10 or so, the error message is upgraded and it got a lot easier to figure out. Like I said, eventually it does click. You wind up figuring out ways to rewrite your code so that you just don't have borrowing problems at all. And generally that winds up being a slightly more functional way of programming, so less mutation and more streaming of data structures. Rust has the concept of stable versus unstable features. Well, there's some useful APIs and pieces of syntax that are still unstable. Rust is a young language, it only stabilized about two years ago now to the 1.0. As the language matures, less and less should be unstable and more useful features should migrate into the stable path. Since it's so young, many useful libraries are still immature. Rust has very good built-in support for threading, but Async.io is still kind of new. They haven't gotten a really good, well-banged-on solution for Async.io. There's something out there called Tokio, but it's only hit the second release within the past couple of weeks. The last thing is cargo kind of locks you into its build methodology. It's hard to do builds in Rust without using cargo. The Rust developers upstream love cargo and don't really want you calling the compiler yourself. It's not to say you can't do it, but it's not well-documented and it's not nearly as easy. However, with cargo BitBake, it makes it a lot easier to pull it into a Yachto environment. Like I said, Rust is a really young language. It only hit 1.0 in May of 2015. Six-week release schedule brought us to 1.15, actually 1.15-1 as of February 2017. More APIs are stabilizing over time, but compiler changes take longer. They've not specified when they're going to have a 2.0 and have any breaking changes in the compiler, so it just takes a little bit of time for them to get some of these standard library changes in. But there's a very active community writing libraries for Rust. If you need something, you can pretty much go search on crates.io and there's something out there that somebody has written. It may not be complete, but the Rust developers that I've run across have been fairly welcoming to patches if you want to use a library and it just won't do everything you want it to do. But the libraries do tend to be in a bit of flux, especially the really cutting-edge libraries, especially the ones written by the core developers because they like to push the boundaries of what the language can do and will tend to write to the very newest compilers. So you have to kind of watch yourself when you're dealing with core contributor libraries. So if you want to give Rust a try, take a look at www.rustup.rs. It's the easiest way to download the compiler and the tool chain and manage all of that. A lot of distros don't have support for Rust yet, and so what this does is it installs into your user directory, basically, so that you can manage the updates and lets you easily update every six weeks or so when they release a new version of the language if you so choose. Are there any questions? Yes. Okay, so Rust supports x86. It supports ARM. It supports Windows, Linux, Mac. It has some support for iOS. It has some support for Atmel, like the Atmega series. They've gotten a little bit of support in for that, but most of that is unstable. But ARM x86, that should all be good. I think there's some support for PowerPC, but it's an unstable distro, so I think you have to build it yourself. But I've not run into a place where I've been trying to use it that it hadn't been supported. Yeah. Yeah, so what he's saying is they have different tags and they say, you know, fully supported, guaranteed to build, but might not completely work and try it and see. But yeah, their website lists all of that out. Yeah, that should work with Rust. So Rust has two different libcs, because that's also in that architecture. So they do glibc and they do muzzle for Linux systems, so that should all work fine. Others, yes. So they do have debugger support. They use GDB. They put all the symbols in. They have a wrapper script around GDB to make it so that it will decode all the symbols correctly. But I've debugged Rust code, and yeah, it works fine. Yes. Okay, so the question is since the compiler does everything statically, how would you do an incremental build and how heavy-weight is your build? So I'm not gonna lie. With the static build, it can take a little bit of time to build everything. Now, once you've built something, so say I pulled down 50 crates or something, so I'm building everything up. I built those crates once. Those don't have to get rebuilt unless they update or unless I clean. But yeah, the incremental build can use some work, and there's active work in the Rust community to get incremental builds a lot better. I don't know what their schedule is on that right now, but I know that they're actively working on it. Yes. Yeah, and Rust cargo tracks the dependencies between them all. But yeah, if it's your own project and you have just hundreds and hundreds of modules, then yes, it's going to have to build your project completely every time, because it has no way of knowing until it builds it all what has changed, until they get the incremental build support in. Yeah, build size and the final executable size can be a concern. One of the advantages of Rust, though, since you're doing this statically, they can eliminate unused functions. So you may be pulling in a lot of code, but if it's not ever used, if it can determine that it's not ever used, then that will get thrown out at link time. So it will try to shrink it. It does link time optimization as much as it can. Well, you should still be able to do post analysis on it because the code that's not linked in just won't be there. So it would be in your source code, but it won't be in the executable, so you wouldn't see that as you're debugging it at all. I mean, it would just be like any time of, you know, link time optimization, even with C. You know, if you pull together a lot of object files, it'll throw out the ones that you can't use. Any other questions? Yeah. So the question is integration with IDE's and kind of a glib answer. The only IDE that matters to me is VI, so it works great. But there is some integration with IDE's. I believe Emacs is getting good support. It's not an IDE, but there... That's another one, they're actually a project to build the Emacs C components in Rust now. I don't know about IDE, about IntelliJ or Eclipse or anything like that. I'm just not familiar. Okay, well, that's good to know. Like I said, I haven't paid any attention to it because VI works great for me. Other questions? Yes? Say it again? So right now, we package it all together, so the dependencies are fetched down as just additional source packages and compiled all together into one. So it's one of those things that if you have a lot of Rust utilities right now that are using the same dependencies, they're gonna get built multiple times, they're gonna be linked in multiple times. I believe we're using the download directory. The download should only happen once. Yeah, the download should only happen once, but it's still going to get built. So it's not like you're building a shared or static library necessarily. You could structure things so that you are building static Rust libraries so that you're only building it once. That's just not how it's set up at the moment. I know the answer to that. Other questions? Yes? Does it support dynamically link? So there is support for dynamic... Okay, so Rust has support for dynamically linked libraries. Right now, Yachto recipe, we aren't supporting it, but you can do it. The ABI is not stable at the moment. So long as you're using the same compiler, you can build a dynamically linked library, but the next version of the compiler, it won't be able to use it. So you're back to the old C++ days. Yeah, talking more to the cargo side because that's all we're using at work. All right, I still have another minute. Any other questions? Yes? There is support for bare metal. What you wind up having to do is run Rust nightlies because they use... Which are some of those unstable features. But there is support for bare metal, and there's a couple of blog posts about people writing OSs in Rust. So I think there's, was it Redox? And Philip Oph has a set of blog posts about writing an OS in Rust. But I've not personally done it, so... Oh, is it? Okay. Yes. Yes. So they... Rust libraries, you can specify no-mangle on it and export a Rust function that has a C calling convention. And then you can have all Rust code implementing the library and export that C calling convention. And yeah, that works just fine. All right, and I think my time is up. So...