 Hi everyone, thanks for coming by. I'm gonna tell you the tale of two unlikely friends that are up there, and they are very much in love. I'm gonna talk about myself for a second here. My name is Katerina Fay, I'm an active open source contributor, I like tea a lot, and sometimes I play around with embedded hardware. I do some rust things who would have thought, so I was a contributor to, or I guess still am, a contributor to the CLI working group. I write too many crates, most of them are probably useless, but I've met a few people here who told me that one of them isn't, so that's pretty cool. And I'm a member of Berlin RS, which is an implementation group that's starting out to be, so we work on stuff together, we organize workshops, and little conferences, for example, a few weeks ago, we had a mini-con for about bind gen, which was pretty fun. So, before I really start talking about this, I wanna answer this simple question, why? As in, why would you do that? So, there are lots of, I have people who tell me that if you have to reach for FFI, your day has already basically gone wrong. And I think that's very wrong, I think there's very good reasons to use FFI between languages, to make languages talk to each other, and especially with Rust and Native Code, to embed Rust into said Native Code. I could probably, this could be its own talk, so I'm just gonna list a few up there, and if you find yourself in the situation where you wanna grab, where you wanna reach for an FFI interaction between C or C++ and Rust, just know that it's a valid thing to do, even though there's people on the internet who will tell you otherwise. But I realize that that's not really the only meaning of the word why, it's also why give this talk. So, about six months ago, I was working on a project and I wanted to do exactly this. And I encountered a lot of roadblocks and a lot of stumbled a lot. And Rust promises to be efficient in its C bindings. And I mean, I know what the people who wrote the website wrote ment with efficient, but when I have to do a lot of research and there's not a lot of documentation about it and basically all the knowledge that I get for this is from blog post, I wouldn't exactly call that workflow efficient. So, my hope was to engage people a bit more in documenting things, in experimenting with things, and building tools that are more publicized than what already exists, where everyone basically has to reinvent the wheel. So, the second thing I wanna get out the way before we really get started is I wanna talk about ABI's because a lot of people don't know this. ABI stands for application binary interface. And it's basically like an API, but for your linker or your compiler, it's not something that humans would ever interact with. It specifies how a function looks in memory. It specifies the way that data is laid out in memory, and depending on the ABI, you can make certain pieces of code talk to each other, whereas if the ABI is wrong, it won't link and it won't compile. In Rust, we use the extern keyword to, so this is a Rust talk after all, you will sometimes forget. In Rust, we'll use the extern keyword followed by a string to specify the ABI that we're using, for example, extern C. For structures and enums, we need to use the reprasee macro to tell the compiler that we want it to look like their C variants. Now, you might have noticed that's a string up there. There's other stuff that you can put in there. By default, it says extern Rust. You can put that in your code. It'll not change anything. You'll have made your code worse to read, but it's not gonna make a difference. There is a lot of different ABI's that you can target. Most of them are platform specific and usually you will just use extern C unless you're doing something super funky, at which point my hat's off and good luck. Rust doesn't have a stable ABI. So when Rust was made stable in 2015, that was a promise that code that you wrote one day would still work the other. And that promise extends pretty much to the language itself, so the syntactic features and the standard library. It does not, however, extend to the ABI. There is this issue by Steve Open, number 600, this that Rust might want to define an ABI at some point. Because this is also a talk about C++, there isn't one standardized C++ ABI. It always very much depends on the compiler that you're using and a lot of different things. This could be another 30 minute talk, so I'm just gonna gloss over this. There are certain things that are standardized, but most of them aren't. So when we deal, when we talk about the C ABI, there is no such thing as a C ABI because C never standardized or specified an ABI. What we mean instead is whatever platform that we're building on, and that is, and hopefully stable. So yeah, a little background on that. So let's get into some FFI. And I wanna do it the other way around when this talk is advertising first, because I think it demonstrates a lot of the same principles and it's a lot better documented. That's why I usually call it boring FFI. There is an entire chapter, or like a section in the Rust book about it. There's a lot of blog posts, and just generally if you wanna do this route, there's great tooling, and generally people know how to do it. So the way you go about using C code with Rust is by using externe functions that are declared as the ABI that you want to use. You need to wrap those function calls in unsafe because the compiler can't verify that they'll actually be there when you run. And you may have to make data compatible in a way that C can understand because Rust is smarter than C in this aspect. The work falls onto you with a bunch of stuff. So this would be an example. We have a function called reverse, and it takes a string and returns a string, or sorry, a constant character array. We call this function in an unsafe block, and you can see here we use the CSTR type to transform our string slice into something that C can then understand. In the reverse, like I've left the reverse, you would have to do the same thing. I didn't want to make the code example unnecessarily complicated. So there's two type, there's two modules in the standard library. One is OS raw and FFI, and both of them deal with transforming data into a form that C will understand. OS raw are primitives, whereas FFI are things like string that becomes a C string. It's an owned allocated string. There is STR, becomes CSTR, which is just a string slice. And then there's lots of primitives. So void and C becomes C void and et cetera. I'm not gonna go through all of them. When the documentation of this is really great, there's good examples in the standard library, so this is usually not an issue, how to transform data. Okay, so let's turn this around, and let's look at what you actually have to do to call some rust code from C. We use the same mechanism with the extern C as previously, except with a little difference, which you'll see in a second. You have to take data in a C form, so whatever data C provides, you then have to make rust compatible, not the other way around. But there's also a thing, you have to declare your functions as no-mangle, because the rust compiler mangles function names differently than a, say, GCC would expect. And as such, you need to declare them as no-mangle so that the C compiler that then links you in your application can actually find your functions. Usually, if you have function errors, it's probably because you forgot no-mangle. It's because I forgot no-mangle, at least. So this would be an example. So it's a function called reverse, it takes a string, returns a string. It's no-mangle, it's extern C. The implementation isn't really important right now. I wanted to do an example where, safely, reverse unicode, and there's a great crate for that, but the example ended up being half my slide and I just took it out because it's really not relevant. At this point, you will need a few more fields in your cargo tumble. So there is the lip section, which I don't usually use unless I'm doing this sort of stuff. You can give it a different name if you want it to be different from your normal crate name. And you also have to provide a crate type. Cdylib will generate a shared object file, whereas staticlib will create a statically compiled library, as the name might suggest. But something to keep in mind at this point is that cargo does not own your project. So cargo will build your Rust code, but it's not in charge of your actual application. That falls to whatever build system you're using with C or C++. I had a wonderful conversation over lunch about how wonderful the situation with C++ build systems are. So whatever you'll use in that regard, we'll have to understand how to link in a library that is generated by cargo. One thing, so in this example here, I'm using CMake. All of this code will be online after the conference so that you can go through it. One thing of note is the reverseo.h header in which we declare a function which you might recognize. So basically, every function that your Rust API exposes needs to then again be declared in a C header so that your C code can actually use it. Okay, so far so good. Calling this from C is then actually pretty simple. We include the reverseo header, we generate, like we use a very unicode greeting, and then we call our Rust code that will reverse this for us. And when we call it, we get that out. Funnily enough, the creator was using couldn't deal properly with all of the composite emoji so it's, yeah, it reversed them individually which means computer woman is not the same as computer woman. So that's pretty much it then. Everything works, like share and subscribe. Okay, not quite. So I see some problems here. The first one is that I'm notoriously lazy and I don't want to write headers. And not just because I'm lazy, but because if you have a header in your repo and it's out of date from your library and then you get weird linking errors and you don't know what's going on and then you find out that there's like a merge conflict from three weeks ago because someone like pushed a button too quickly on GitHub and that's why your application doesn't build. That's not really cool. The other thing is this was a very optimistic function. It returns a string. What could possibly go wrong? So maybe we might want to fix that. And the thing is Rust makes it very easy to forget about memory management. So how does that happen? And the last point is less of a problem. It's more of a philosophy, I guess. I want to talk about what even makes a pretty API because this isn't just about making Rust talk to see. You can make anything talk to anything. This is about efficiently talking to see which means making it pretty. So let's start with the easiest thing first, tooling. This could be another 30 minute talk. I think you're seeing a pattern here. It's a lot of stuff. So there's this project called C bind gen which is pretty awesome. It's basically like bind gen but in reverse. So you point it at a crate and then it'll generate header files for you. You can either do this in your build RS in Rust code or it provides a CLI that you can then invoke in your favorite build system, which you probably have. Yeah, so when it comes to actual build system support, you have to do a lot of work. So usually I find that writing a few bash scripts that hook into some make file or something is usually the easiest to deal with generating, like invoking cargo, building it, then taking the library from the target directory, putting it somewhere that your build system will understand and then letting the build system do the rest like it would normally do. But this isn't a thing where you can just go somewhere and look it up. It's figure it out yourself, which isn't very nice. So let's talk a little bit about memory management. The thing that Rust doesn't really need you to handle. By default, when you create an object in Rust, it creates it on the stack, which is really convenient, I guess, if you don't need it to be on the heap. And in most situations, this is fine. There are situations where you might want it to be on the heap and then you make that explicit. And especially when we talk to some other piece of code where we have no idea what it's doing, what it's doing to the stack, it's not a great idea to use the stack as permanent data storage. So put your troubles in a box. The box is a heap-allocated pointer in the standard library and whatever thing you put inside of it will then be on the heap and you, this code up there, the extern C, the box thing, what that does is return a pointer to whatever you put into memory. And the thing is we declared our structures repRC, which means that C will be able to follow that pointer and then do something with it. The other thing is that boxes retain type information where they store the data, which means that you can also get the data back. So you can, if you have some piece of code, say PTR, and I just realized that code example is wrong, it should be PTR that you do reference, not CTX. So, whoops. You get a C void, which can be literally anything, and then in an unsafe block, you essentially cast that to be a mutable reference to my thing because you can follow the pointer to wherever the thing is in memory and then you can use that and then you can, in your Rust code that follows, safely access all of your stuff without having to worry about something going wrong. This is what this looks like from the C side. So basically, yes, the make thing function just returns a pointer, the my thing pointer is then on the stack and we can deal with this. Something to remember though is that you can't make your application memory safe. So the Rust's guarantee ends at when you return back into C code, which means that you should not make any assumptions about persistence of data. It might be gone, something might write over it, or it might just die before you can do anything with it. So always be prepared when you build these APIs for them to have a certain amount of hardening against C code doing something stupid. Even if it's like unintentional or a bug, it's something that you have to deal with. Speaking of things exploding in your face, let's talk about errors. So errors and generally reporting if and when things go wrong and see are a little weird. So in Rust, we have these amazing types result an option that let you return something from a function and it contains some type information or some state information about what state it is in. The result realistically communicates the state if it's okay or an error and the option just communicates if it's existent. And that is super convenient because you can just return something from a function and I don't have to explain this to you, you probably know this. Well, this is something that you can do in C but before I go there, I wanna talk about something because this talk is called C and C++ and I've really only talked about C so far. We have to split this up in errors and C and errors and C++ because errors and C are something that we can deal with. We have a binding to C code and we can deal with interacting with C code. Errors and C++ are either exceptions or some class that uses a template to essentially be the same thing as a result just in C++ and that's not something that we can really easily interact with and even if you could, it would be very dependent on your compiler version and it's not something that you ever want to touch. So whenever we talk about errors and whenever we talk about building pretty APIs, you have to keep in mind that you build a layer to C from Rust to C and then your C++ code if you have it will have to use that C code just the same way like externally as your Rust code would. So you basically have two parts that sort of communicate via a common ground that they both don't really like but they just put up with it. So this is something that we can do. We can emulate a result in C. So this is a Rust struct obviously and what we can do is we have some struct over a generic T, we have a box in there so that C doesn't have to understand how big this is because in the end for us it's just a void pointer and we can mark that field as economy or like hide it or something as long as the structure knows that there's a pointer in there that points to something, it doesn't have to care. So this way you can deal with returning Rust objects that you might want to give to other Rust functions that C doesn't have to deal with while also returning an error code that when it's convenient. And this is what this would be used then. So we call some function, this is C code, we call some function called my function and the return value is this wonderful result type and after the function was called, we can just call code and if that doesn't trigger and we don't have to deal with an error, it's a valid thing. There's another pattern in a lot of C libraries and I've been trying to look at how it evolved over time. It seems to have like a lot of older libraries use this but a lot of newer ones as well so it's not really sure, like I'm not super sure about a trend that use double pointers and so as a little bit of a refresher I guess because Rust developers usually don't deal with pointers directly because we don't have to, the idea behind this is that you provide the pointer to a pointer to something and then by dereferencing one of them you gain access to the other. In this case we have a variable which is a pointer on the stack and we give a pointer to this pointer on the stack at which point in the function we can de-reference the first pointer and write onto the stack outside of our function. This is a pattern that you can use in C to do error reporting by always returning an integer from your function you have the ability to communicate if something goes wrong, if it returns a zero it's fine and happy or it can return some error code or an enum or whatever while handling data return types with double pointers where you make some field on the stack so we have this function called getClient and we have a client pointer on the stack and then we give a pointer to this pointer to the getClient function at which point it'll fill it in and if there's no error the client variable will then contain the pointer to a valid client. You can do the same thing for initializations you can also provide parameters obviously that have nothing to do with any sort of return value and just yeah, this is the same pattern essentially. So from Rust this is super unergonomic to use basically what you're getting so let's take the initialize function where we have a pointer to a pointer and a port. We get in a star, star, starMute, starMute, C void and a port, you can check if the port is valid and then return like an error if you wanna allocate like port minus one or something and then what we do is we generate like we create a new server in Rust we put it on the heap with a box and then we use the box into raw function. What that does is it destroys the box and turns it into a raw pointer so Rust does have pointers, they're just unsafe and so whenever you want to deal with an actual raw pointer to something where you can seriously shoot yourself in the foot you have to put in an unsafe block and you then also have to deal with the lifetime and the cleanup of this piece of data yourself so you can actually this way you can leak memory quite beautifully because you can put something on the heap, give yourself the raw pointer and then never do anything with the raw pointer again and it's just, it remains on the heap. But so it's pretty ugly from Rust but if your priority is to be in line with the rest of a C API that you're trying to match this is really great because it feels exactly the same. You can even like the server struct can be exported to C so that it can directly access the port if you want that to happen and if all of your other code uses this pattern or if some of your other code uses this pattern it's a good way to sort of match that and this is like literally figuring out the box into raw was why I then wrote this talk because there's like no documentation about this. Well, there's now but. So let's talk about C++. Well, I already mentioned that we'll have to wrap some stuff and what we can do is so I'm not, I don't write that much C++ code. I know that people shouted each other about whether or not to throw exceptions so I have one example that throws an exception and one that doesn't. Basically what you will have to do is include the header that is generated for you the same way that you would include any C header from C++ and then you would probably want to write a module around it that calls into it and then abstracts away any sort of errors into the system that you're using in your code. This is very specific to your project so if you use exceptions then throw exceptions or if you use like a result struct that you're emulating because there are things like that you can use templates or something you can return that instead. So at this point it's very much down to you and your project what you need. The important thing is that with the patterns that I showed you in C at least you have a good basis for communicating errors in the first place because the worst thing you want to do is in your C++ code just throw an oopsie exception that tells you that something went wrong and you have no idea how to debug it. Okay, so I have five minutes left and I hope that until this point I've shown you some useful information and maybe shown you where maybe the ecosystem needs more work. For the last five minutes I want to answer this question which has been haunting me for the last six months or so. So I think if this isn't your reaction by the way then I'm deeply worried about you. And well the answer is yes you can. So let's look at what exceptions actually are maybe you've never worked in a language that has exceptions like see or rust or you've never looked under the hood deeply enough. This is based on the C++ LLVM documentation which is really great. So if you're curious about that definitely read that. It's really well written. So there's usually three keywords involved sometimes four but like we only care about three right now. Try, throw and catch. What a try does is create a sort of landing pad that you can jump into later and I'll go into the details in a bit. Then when you call throw at some point in your code what you do is you start walking up the stack the same way or similar to how you would unwind your stack. You go up until you find the landing pad and you jump into it. Catch blocks are then basically function pointers that you jump onto and like call them. The thing there is that there is a bit more logic to handle which again I'll deal with it in a second. So the try, the landing pad actually contains more information than just how to deal with an exception because the try block can be ended in two different ways either you can have thrown an error or you can have not. So it needs to understand the difference between or it understands the difference between an allocated exception that sits somewhere in a buffer and not at which point it'll either choose to evaluate the catch clauses or it'll just continue in your code as normal. So the catch is actually a filter because in C++ at least you can catch for multiple exceptions at the same time kind of multiple catch statements or catch blocks I guess that filter for different exceptions and you can even like use the pipe logical or operator on them. And maybe you don't have a catch block which catches the exception and then it gets rethrown in the hopes that someone upstairs will care. The last part is actually the easiest and that is nice because that's what we care about. So the throw keyword gets replaced by the compiler with calls into libc++ that allocate an exception define types with certain mangling rules and then throw the exception. So this is a talk about Rust and I'm sorry if I went a little far there introducing exception RS. So I wrote a crate that can throw exceptions C++ exceptions in Rust. This is what it would look like so you have the exception thing and then you can like throw some structure with whatever payload you want. How's this implemented? Well, there's no libc++ bindings in Rust which is probably a good thing. So instead I wrote a little shim layer in C that just invokes those functions that are exported. So those are the two functions. There's like a third but it's not important for this example. These are the two big functions that you need to allocate an exception and to throw it. Those declarations are in the C code exactly that way because I can't provide those and I am compiling my code in the hopes that before it's run, a C++ compiler will look at my code and go, oh yeah, I know where to find those. So you're very much dependent on your project being compiled with a C++ compiler or else it won't work. And this is what that looks like now. So the cargo build release is so that I build the actual Rust code that throws the exception and then you invoke a G++ or a C++ compiler that links in the exception throwing library and then it's, well, I didn't handle it because I thought it looked nicer to die with a custom Rust exception. All right, so there's one more thing that I've been working on and I kind of got it to work. It's answering that question. And the answer is yes but I'm out of time and I will write a blog post about that because it's a whole different kind of worms on that adventure. So thank you for real. So follow me on Twitter, I do a lot of shitposting if you can put up with that. Or if you have questions or wanna get in touch with me, if you see this online at some point or you're shy or you don't meet me, write me an email so I do like getting emails. I want to thank my employer for our systems. That's the name you might be familiar with. It's the company that I'm working on in with James and they sent me here. So I wanna thank Mozilla for allowing me to be here and providing accommodation and stuff. And I wanna thank all of you, the Rust community. I really like the keynote this morning because I talked about how the Rust community can be inclusive and welcoming and it's really one of the reasons why I like contributing to Rust. So thank you and yeah, have a nice day. Thank you.