 Okay, so we're going without speaker notes here. So if I stumble for a second, then bear with me, but this is getting something for nothing. So I'll put this up here while I'm talking. So if you wanna take a picture, I'm James Munn's. I'm an embedded systems developer. My background is in developing everything from avionics to IoT connected devices. I'm bitch and maffed on Twitter, and here's my email. So if anyone wants to take a picture of that and come talk to me, I would love to hear from you all. So this talk is sort of about embedded systems. But what this talk is really about is how we can teach the Rust compiler about what we're trying to do with embedded systems to get it to enforce what we want it to enforce so that we're not manually checking this out. The concept of getting something for nothing or as it's more commonly called zero cost abstractions is not super new to the Rust compiler. In fact, if you look at the website, it's literally the first feature that they put on the website. So zero cost abstractions are something that's really, really intrinsic to the Rust language itself. Also, if you haven't, so how many embedded developers do we have here? Who's worked on a microcontroller or a Raspberry Pi? Okay, awesome. So the use of Rust for embedded systems isn't super new. Back in 2014, the crew from Pebble, like the smartwatch that was on Kickstarter, actually had a user space application written in Rust. So this is in August of 2014, so this worked with Rust 0.12. So not super new, but we've come a long way since then. So Rust in 2018, especially for the 2018 Arrow release, has a lot of stuff. In the keynote this morning, they talked about all the stuff that's stabilizing this year. And some of that's things that you notice when you're developing Rust on any platform. But there's also been a lot of things that have been stabilizing for embedded systems. And a lot of it has to do with the weirdness of running code outside of an operating system. What do you do if your code panics and you have no operating system to crash your program? What do you do the first thing you do after you power up? What starts your application? How do you tell the Rust compiler to say, no really, just start running this application. There's nothing else here to do. Luckily, Rust has the backing of LLVM. So we're using LLVM as a compiler backend. And LLVM has already been used for C and C++ to target these embedded microcontrollers. So kind of the really low level plumbing has already been done in LLVM. So getting Rust to support things that already exist in LLVM is a much more straightforward process than if we were trying to bring all of Rust to a microcontroller that it had never had a concept of before. Also, when we're working on these embedded systems, you typically don't have an operating system. And the standard library takes for granted a lot of things that an operating system provides, the ability to open files and sockets and dynamically allocate memory. And that's not necessarily guaranteed to exist on this embedded system. But luckily, we can throw it all away by marking whatever we're building is no standard. And then if we want, we can progressively add these features that we do wanna support back into what we're building. And with all of the things that are stabilizing in 2018, it's our goal, I'm gonna say are the embedded working group who's working in getting Rust working, or embedded working with Rust, is that you can just install the stable version of Rust, do cargo build for one of these microcontroller targets, and you get a working microcontroller binary. Building firmware doesn't have to be complex and the batteries are still included. You still have all the great things that come with Rust and all you had to do was install Rust. No downloading tons of dependencies and making sure you have the right version of GCC and getting all, yeah, none of that. The idea is just like you build a Rust application. So I've been talking a lot about embedded systems, but I haven't done a good job of defining what embedded systems are. So my tongue-in-cheek description is computers you don't sit in front of. And this could be anything from the TV remote sitting on the coffee table at home to the engine control system for a rocket. It could be satellites. It could be electronics and airplanes. All of this are combinations of hardware and software that were designed to do one thing and do it really well. Not like a general purpose computer, but very purpose-built system. So this definition covers a huge range. Like I said, everything from your TV remote to a Tesla would be an embedded system. But we're gonna focus, at least for today's talk, way down on the smaller side of this. So microcontroller systems. This is way closer to an Arduino than something like a Tegra dev kit that can do machine vision. So these microcontrollers have a phenomenal amount of hardware power's ability to react to the real world while being really itty-bitty tiny. So specifically, this is an example of a microcontroller. This is a Nordic NRF 52 Bluetooth chip. And if you have some Bluetooth devices in your home, chances are this is what's powering at least some of them. So I actually have this board. So this is a really big picture. But this is the board that I'm talking about. Ooh, I'm on the top side now. But this is a whole development kit. The actual embedded system is in this, well, it's red square up there, but this tiny white square here, which is a little smaller than a postage stamp. So an entire running computer system fits way smaller than a postage stamp and they actually get way smaller than this. This is like a medium-sized one. So these little microcontrollers have CPU and RAM. And when I say RAM, I mean like 20 or 64 kilobytes of RAM and tens or hundreds of megahertz of CPU. But that's not all they have. They have this hidden superpower called peripherals, which are a way of interacting with the real physical world or electronics that help you interact with the physical world. The cool thing about these peripherals is they can go off and do stuff while your CPU is off doing something more important. Kind of like offloading computation to a GPU, but for a lot of really domain-specific, interesting things. And the nice thing about being able to offload this is that your CPU can go off and do something better while this is going on instead of busy waiting around or it can do nothing and save power by going to sleep. But unlike your GPU, which has a software API like Vulkan or OpenGL where you've got a real software library for dealing with this hardware, they don't have that. They have a hardware API. So they have a way where your CPU can shove some bits and bytes in certain places and that gets your peripheral doing the things that you want it to do. So these are called memory map peripherals because the way they're exposed to your CPU is there's a chunk of memory at an arbitrary location and you have to know if I write a bit here, it will do something. If I write a whole byte here, it will do something else. These microcontrollers have a real and linear 32-bit addressing space. So these are 32-bit microcontrollers, which means everything from 0000 to FFFFF is a real location and you can write stuff to it. So these are real places and there's nothing like an MMU that's gonna protect you of virtualizing the memory or making sure that you have access to certain tables. And because these microcontrollers only have 10s or 100s of kilobytes of RAM, there's a lot of extra room. This is four gigabytes of address space. It's a lot of room. So the developers of these microcontrollers said, well, we're only using 64 kilobytes of it for memory, so what can we do with all of that extra space? So what they do is they design a system that looks kind of like this, and this is a really dense diagram from a datasheet, but this shows you everything from zero, like that's where your flash nonvolatile storage lives, all the way up to F, which might be a ROM table or something like that. So this is the memory map of this specific processor and you see over on the right, there are some peripherals up there and that magic stuff that allows you to interact with the real world. And one of these registers, so this 132-bit register is the SPI Peripheral Frequency Configuration Register for the NRF 52 and there's also a lot on here, but the important part is, is it shows you the different values. So if you write 80000 to this register, this serial port will now begin sending at eight megabit per second and all the registers work like this. There's arbitrary stuff at arbitrary locations, you've got to read these huge datasheets and figure out where you're herding your bits from where to where and put them in the right place and stuff like that. This is a microcontroller development at a very low level. So great, we have a language that works with microcontrollers and we've got a great way of working with them so now what do we do? We write code, that's obviously the first thing you do before planning. So you might write some code that looks like this. You go I've got this serial port at this memory location so it's a mutable reference to a 32-bit word so this is my pointer so I might want to make a function that reads and because this hardware is interacting with the real world, this memory can change out from underneath you so it's a volatile read or a volatile write. So you write this, you wrap the function in unsafe so that your user doesn't have to call unsafe every time you call it and this is kind of gross to look at but it does work and you can read this serial port speed and you can write this serial port speed but it's not so great and if you're coming from a Rust background you might go this isn't really what Rust library code looks like, right? I want something more rusty. So you might break it up into a struct you might call a serial port struct where you organize this and now you've nicely hidden this pointer in the arbitrary values inside of the struct as constant values so you can use them and you've given yourself a way to create this new serial port and then you might give it like member functions where you can do this so now you can go serial port dot read speed, great. This actually works so you can in your function do something. You get one of these serial port handles you read the speed and then maybe you do some work and then you're like now I wanna go high speed and you change the speed and this kind of thing would work but the problem with this is if you notice I just kind of conjured the serial port out of nowhere and the problem is if you can do it once you can do whatever wherever you want so if you have a slightly bigger example where you're setting up the serial port making sure it's the right speed and then you call some other something else function that you worked on a couple days ago and okay now we can get started but the problem isn't that other function you also have one of these and this is changing the speed out from under you and this is not a great thing because if you wrote those code on different days or with two different developers or months apart you're gonna forget that these things are related and now you've got mutable aliasing pointers which is kind of the whole reason we wanted to switch to rest was to avoid these kind of problems so this is smelling like mutable global state and unfortunately hardware is nothing but a big collection of mutable global state which is painful if you're coming from a Rust background so if we wanted to fix this if we wanted to set some rules how do we avoid global mutable state well let's come up with some rules the first one you might come up with is oh yeah what are our rules if everyone just has read access to everything we should probably let everyone have it because you're not touching anything you know everyone can look and that's not gonna hurt anyone but if someone has read write access to these it should be the only one because otherwise all the other places are gonna be confused because they thought that they had a read only handle to something now if you're paying attention these rules sound really familiar and it turns out these are exactly the rules for the borrow checker so how can we use the borrow checker to enforce these ownerships and borrowing of these peripherals so we can get the kind of really rusty behavior from a library that we want but it also works on real hardware well the important part with the borrow checker is it has to track this ownership and moving around so we can't just conjure these out of nowhere we have to have the one serial port that we're tracking it as it's moving around and we give away references to it and things like that so this pattern in CS is not new to Rust they're called singletons it means that you can only instantiate a class once so there can be only one I couldn't find a good Highlander gift to put here so if anyone wants to send me a good Highlander gift and I'll add it to this presentation so you might say okay I need one so great I'll make it a global mutable variable so that way there's only one well I'll just be talking about this one and it kinda works but the problem is in Rust when you have a mutable global variable everything you do with it has to be unsafe so it's never gonna be very convenient to use and the problem is this actually doesn't help us get to the borrow checker because this giant global mutable variable is accessible everywhere and we're not tracking it moving around different places it just is so this would work but this isn't really what we wanna be doing instead we could write something like this we could write a struct called peripherals which has our serial port as an option inside of it and it has a function called take serial where the first time you go to it it unwraps an option and it gives you back the thing that was in that option and then it replaces that slot with a none which means the next time that you call it it's gonna panic because you're unwrapping a none so this allows us to take what you need but only once and this comes with a little bit of runtime overhead you have to have an option type you have to actually take it out of the field and give it away but this is only a little bit of memory and a little bit of CPU and you do it all up front at the front of your program and it unlocks this huge ability to use the borrow checker to move stuff around and verify that you are moving it around and lending it out the way that it's supposed to be moved around and lent out so this is a small runtime overhead with a huge impact for what we wanna be doing and I wrote my own but this is Rust you don't have to rewrite code that someone else has written in the Cortex-M crate there's a singleton macro that does this for you and if you use RTFM which is kind of like a really lightweight operating system for these embedded devices it actually even handles it all for you in macro so you don't even have to think about unwrapping it or taking it just on the initialization it goes here are all of the peripherals and now you have all of them and do with what you want so again, you don't have to rewrite this from scratch there's stuff out there but I wanted to show you kind of how it worked under the hood ooh, excuse me but why? Well, we have singletons and they're related to peripherals and we have this but okay wait why is this useful again? How does this singleton make a difference? Well, it turns out that the most important part is that reference to self because when you have these structures and you're required to have a reference to self to use this structure and there's only one place that you can get this structure it forces you to pass this ownership and borrowing around so you have to take the serial and before you can ever even think about reading the speed of that peripheral you had to have gotten it from somewhere and if Rust is able to help you track where it's going and where it's coming from now you can't do it unless you've made the borrower checker happy which is really cool so it also gives you the ability to understand the software you write a little bit better if you have a function up there called setup SPI port and it takes a mutable reference to an SPI port which is a serial port and a GPIO pin and if it's mutable reference you're like okay this can probably change my hardware this could if it wanted to change some stuff out from underneath me but the second one that just reads a button that only takes an immutable reference you go okay well this probably isn't gonna change anything so this is again getting you thinking in embedded systems the same way that you work in application development in Rust this struct starts acting like a normal struct even though under the hood it's this really cool hardware mapped peripheral this allows us to enforce whether code should or shouldn't make changes to hardware entirely at compile time there is no runtime checking in this the compiler is making sure of this for you not your runtime so there's a little asterisk there the little asterisk is this only works across one application because Rust only has a concept of tracking these borrows in ownership with one application but for these microcontroller systems there is no OS running multiple applications there is the application that you are running on your microcontroller so this is a problem but not for us before you worry all that stuff about writing registers and stuff like that that look like a lot of code especially when you have to do every feature of the microcontroller but don't worry this is Rust and it's 2018 there's a tool for that it's called SVD to Rust and it takes XML files that are provided by the manufacturer that have every register every value everything like that and it vomits out tens or hundreds of thousands of lines of usable Rust code with comments with modules nicely laid out it's kind of like bind gen but for hardware so now that peripherals are structs what else can we do with Rust we've figured out a way to map these but what can we do we've got the type system we haven't touched on that yet we can put these strong types to work so scaling back a little bit microcontrollers the smallest most simplest thing you can do with them are use their GPIOs or general purpose input outputs this is like a single pin if you've used Arduino it's like digital input, digital output they really read one bit of the world at a time low voltage, high voltage so if you were writing one of these in Rust you might have a GPIO pin and it starts maybe unconfigured and so you also need an input GPIO and an output GPIO so I've made these structs here and when we've got our initial GPIO pin it's not configured if we want to read a button we've got to put it into input mode so what we might do with the type system here is take a function or give a function to GPIO that takes ownership of a GPIO pin and the return type is an input GPIO type so it consumes the original GPIO pin which is an unconfigured item configures it the way you want it and gives you back something that is an input pin so you're kind of making these state transitions using the type system so let's look like what we could do with one of these output GPIO types we might wanna come up with a driver for an LED so the LEDs are the little blinky lights on these embedded systems and blinking a light for embedded systems is like hello world you don't have a screen so how are you gonna say hello you're gonna blink a light so our LED driver we might want to be able to create a new instance of this LED driver and we say well to make an LED driver you have to tell me which output pin you're gonna use so it needs to consume this output pin and give you back an LED pin and then once you have this LED pin the LED pin has a function called toggle which requires a mutable reference to self so if you wanna toggle an LED you have to go through the motions of taking an uninitialized GPIO pin turn it into an output and then turn that output into specifically an LED driver and if you don't go through this transition then the types this isn't gonna let you do it it goes GPIO pin has no toggle method on it what am I supposed to do here you need to go through and this should start to sound a little familiar if you've used the builder pattern in Rust before it's forcing you to go through these transitions to get the structure that you want that has the interface that you want on it this allows you to enforce design contracts entirely at compile time again with no runtime cost and no room for human error all of this exists only at compile time you only get access to it if you've made the compiler happy now what do I mean by no runtime cost so all of these structs that I've shown you the GPIO pin, the input pin, the output pin there's nothing in them there's no memory to take up why would we allocate space at runtime if nothing exists these are exactly the same size as like the tuple type if you've ever seen or an empty tuple type in Rust and what that means is that at compile time Rust treats these like real objects and moves them around and make sure you have borrow and you can take references to them but then it goes, but wait a minute we're not talking about anything here so at compile time this all evaporates off so you've done all of this checking at compile time and this doesn't even exist there's no code that checks this at runtime it's not even a no op it just doesn't exist in the final binary these are all zero size types they act as real types at compile time but they don't exist in the binary they don't take up your RAM they don't take up your CPU time they don't take up your space they take up your compiler's RAM and CPU time but not your microcontrollers and that's nice cause we might have like an eight core build machine building this binary and a single core 16 megahertz processor running that so we wanna offload all the hard stuff to the big powerful machine can we do more with types? Yeah, we can do more with types so what if our output GPO has multiple modes and it does cause these microcontrollers have a million different specialized features cause they want you to be able to interact with all the stuff in the real world that you really want so we might have this output GPO might have push pull mode which is kind of like a general purpose turn an LED on do something a little bit here and there or it might have an open drain mode which is really good for like driving a motor or an LED that requires a lot of power and they might have different characteristics so we could make our output GPIO generic over these different kinds so maybe by default when you just create one of these the hardware is by default in open drain so when we have our default you'll get back an output GPIO that's in open drain mode and again the same way that we did those state transitions before we can do the same thing with generic types so you can turn it into a push pull and you'll get back a flavor of that structure like that using generics or you could put it into an open drain again and it will give you back a flavor of this now the cool part here is if push pull and open drain are zero size types and we put a zero size type inside of another zero size type how big do you think that type is? It's still zero you can go infinitely deep and have as many of these abstractions as you want and if they're all zero size type you could go 30 layers deep and it still evaporates away at compile time so you could have a driver that's specified to only work with this so you can, even though it's a generic you say I only take the open drain flavor of this or if you have something that's kind of a little more general purpose you could say I don't even care what kind of mode as long as it's an output pin I can work with that so you could either have one that's very specifically tied to some flavor or you could have one that just says it all works the same anyway set it up however you want and then just give it to me and I'll work with it so that was the borrowed checker in the type system what about the trade system? Rust gives us that too well this is really useful when you're building something bigger if you are writing 10 lines of code to Blink and LED yeah you probably don't need anything like this but if you are building code that's meant to be reused in many many places or drivers that work for multiple kinds of chips you need a way to abstract over that so I'll get into what each of these different parts are but the ecosystem in Rust is looking a little something like this that blue over there is this blue board and you've got the actual hardware you've got your low level peripheral drivers like your NRF 52 crate and you've got your higher human readable interface that says like I wanna send it over a serial port not like shove this data to this register I just wanna send bytes but while you're writing that you could say well there's these traits called embedded how that are a generic way of looking at a serial port it might look something like this like an output pin what can you do with an output pin? you can set it low and you can set it high that's all it can do so this is kind of the interface or the trait that goes with that so this is kind of really generic and this doesn't have anything to do with an NRF 52 this is just how the hardware works so when you're writing your crate you're like well I already wrote a set pin low function and I already wrote a set pin high so let's just like build these into the traits so that if someone else using that trait can just use it with my hardware and then this is a lot of code but this would be something like a driver that says hey I've written this driver for this sensor but I've written it not using any knowledge of hardware just these embedded how traits which means any hardware that implements these embedded how traits can just drop this driver in their package and it just works because you've implemented the interface and you have a good layer of abstraction and this is really cool for us because when you have N chips microcontrollers or whatever and M sensors and motor controllers and all of this instead of to get all of these combinations instead of multiplying these together to get all the possible combinations that are ever so slightly different and never work the way that you want them to instead it's just N plus M which means you have to implement your chips and you have to implement your drivers but the longer we go the bigger this difference ends up being and this makes things actually reasonable to do at scale so unfortunately all good things must end so I wanted to recap what we've gone over today to kind of like solidify the concepts in your head so we talked about how ownership and borrowing can help us as embedded developers we can treat hardware resources the same way you treat any other data type and rust and let the compiler manage who has access to what and what they should be able to do with it we use the type system to enforce state transitions preconditions and post conditions this means we know that if we've gotten one of these structs it's set up the way it's supposed to be and when we leave it gets torn down the way it's supposed to be this is a concept called functional safety in embedded systems if everyone's worked in the safety critical field you can stop trying to keep all of that in your head or in the comments or remembering it day to day if a computer can check it let the computer check it they're way better at checking normal stuff than stuff that's done over and over and over than humans are because everyone has a bad day as a developer and the trade system lets us enable code reuse for your embedded projects no more copy and pasting from the Adafruit library for this and then tweaking it to work with my controller and I forgot to change a variable name and now all of a sudden it doesn't work at all you can just write things for interfaces that really work and for me I'm a big fan of Rust Cargo in the community so anything I can do to stay in this field while taking advantage of modern language features enumerated types, match statements all of that still exists in no standard so why lose access to it and package management which is not really a thing in embedded systems except for a couple little cases and a group of people who are willing to work with you to solve problems and a reminder all the stuff I showed you today is optional you can go and write a giant unsafe block and if all you're doing is writing 10 lines of blinking LED that's probably the fastest way and if it all fits on your screen you can verify all that stuff in your head but as these projects scale or you're writing reusable libraries that are meant to be used by very many people who are not familiar with that hardware you should give them all the tools you possibly can to make sure that they get things right the first time and they don't have to figure things out the hard way so thank you very much I hope you for those of you who are embedded systems developers I hope you're really excited to write Rust now and for those of you who have never worked with embedded systems I hope this is feeling way more accessible and hopefully all of you can get started so thank you all for listening I have a couple plugs before I don't have my timer because I don't have things so I'll go really quickly I'm a part of the embedded working group we're part of the Rust team and we're, that doesn't quite fit but part of the embedded working group we're trying to make things as usable and scalable as possible for embedded systems so come check us out on GitHub that's our coordination repository we would love to have your help and we're expanding now so now's a great time to join I also am founding a company with some of the other people in the Rust community including Katerina who's speaking later today and we're focusing on low level and embedded problems in Rust so if you're a company that has low level and embedded problems or systems level problems in Rust come talk to us we would love to chat with you so thank you very much I'm James Mons and this was getting something for nothing