← All talks

Why Rust is Safe

BSides Canberra · 202553:44124 viewsPublished 2025-12Watch on YouTube ↗
Speakers
Tags
CategoryTechnical
StyleTalk
About this talk
Ben Williamson explains how Rust achieves memory safety and thread safety without garbage collection or runtime overhead. The talk covers Rust's ownership system—move semantics, borrowing, sharing, and regions—demonstrating how the compiler enforces safe pointer usage at compile time, preventing use-after-free bugs, data races, and memory corruption.
Show transcript [en]

[music]

[music] Well, welcome back everyone. By the way, we have the CTF starting right now. So if you want to do the CTF, it is in exhibition hall. I think quite a few people have gone there already. It should be a great event. Uh right now we have an amazing talk uh by Ben Williamson, why rust is safe. So let's [music] welcome him to the stage. [applause] >> Thanks very much, Sylvia, and thank you, Kylie, and to everyone who's been involved in in making this event happen. It's such a thrill to be here to speak with you. I also want to acknowledge Arya Desires. She's uh one of the core contributors to the uh the Rust standard

library and author the also the author of a document called the Rustomicon. Um the talk that I'm about to show you uh is based on her way of explaining Rust and I'm really grateful for her uh encouragement to share it with you here. Um just a note about questions. Uh it's this is this room is a little bit bigger than uh what I'm used to running workshops, but if there's anything that's unclear or that you'd like clarification on as I go along, uh I'd love to be interrupted. So shout it out. I really want to make sure that this makes sense for you. So just to start with some context that you already know, memory safety issues

cause severe security bugs and lots of them. So Google, Microsoft and Misilla uh have all reported that over 70% in in memory unsafe code bases over 70% of the severe security vulnerabilities that they see are caused by memory unsafety issues. Now in some code bases this is not an issue. So if you're writing JavaScript you're not too worried about buffer overflows or you know data races or memory corruption. But there are certain categories of software where really uh you know systems level stuff either for performance or because you're inside a device drive or in a little embedded system uh memory safe languages of that kind where you've got a garbage collector and um a runtime are really

just not an option. And so for a long time the only option has been C or C++. I spent the first 20 years of my career working in C or C++ C and C++. Um, I come from, uh, an embedded systems background. Uh, I worked on, uh, handheld devices, uh, embedded Linux, uh, some browser security stuff. Uh, I worked at Apple for 9 years. Uh, and, um, I worked on a couple of different things there. I worked on, uh, autonomous systems, uh, safety critical systems. I also worked on the iCloud keychain sync protocol, the crypto protocols around that. and working in this systems level stuff. I kind of always assumed well you know if you do

the kind of work that we do where you're you know you need that level of control and performance there's just a certain amount of pain that comes with doing that. There's uh you know you're you're inevitably going to end up uh messing up at some point and you'll be debugging a use after a free or a data race or some kind of memory corruption and you just have to be really careful. The compiler can't help you with that very much. I always assumed that any language that gave you safe pointers would come with some kind of overheads or a runtime that meant that it just wouldn't be applicable in this kind of software. Uh and so then along came Rust and

proved me totally wrong. Um and I've been advocating for it ever since. The first project where I really got to use Rust in production at scale was uh a uh a custom implementation of IPSec uh that encrypts all of the packet traffic between iCloud hosts. Uh and that was a great demonstration of the value of Rust for um a performance critical security critical application. Um I also taught Rust. I've I've taught Rust to hundreds of engineers and I've helped other teams to uh to adopt the language. So, okay, here's the pitch. Um, there are three things you need to know about Rust. One, it's fast. Rust is just as fast as C or C++. Two, it gives you full control over uh,

you know, it's a genuine systems language. It gives you just as much control over memory layout and allocation as C or C++. It doesn't use a garbage collection. It doesn't use a runtime. And, uh, it can work without a dynamic heap. So you can use it embed in embedded systems and in kernels. Okay, so far this all just sounds like C and C++. Then we come to number three. It's safe. If your program compiles then it is completely memory safe and thread safe. You cannot have a seg fault or corrupt memory or have use after free or even a data race. So how does it do that? How can you have full control over memory and pointers

uh but have these safe guarantees? Well, the answer is Rust system of ownership. Ownership is comprised of these four ideas. Move semantics, borrowing, sharing and regions. Move semantics gives us types that represent resources. They cannot be copied. They can only be moved from place to place. Borrowing and sharing give us reference types, safe pointer types, and regions make those reference types much more useful. So this is what we're going to walk through in this in this talk. But first, we need to take a closer look at those safety guarantees. Now, as I said, if your program compiles, then there's no memory corruption, there's no data races, uh, and there's no undefined behavior. There is an asterisk on these

guarantees. These guarantees apply only to the safe subset of the language. Rust is really two languages. Safe Rust and unsafe Rust. And the delineation between the two is really clear. To enter unsafe Rust, you have to type the unsafe keyword. And doing that lets you work with raw pointers and call functions from other languages like C. Now, I know what you're thinking. You're thinking, Ben, a second ago you said rust is safe. Now you're saying safe rust is safe and unsafe rust is unsafe. As soon as I need to do anything with pointers, I mean unsafe rust and all bets are off. How is this any better from better than C? Okay. So the practicality of working with Rust is

that uh is that working with these safe pointer types means that you very rarely need to use unsafe rust at all. [snorts] Um sorry uh and that's because of these safe safe pointer types called borrowed references. uh and they come with zero runtime overhead. They compile down to just the same code that you would have gotten from raw pointers in C, but they have this safety that's generated at compile time. The compiler is able to check the use of these references. And understanding these safe pointer types is really what this talk is about. So because we have them, it's very rare that you'll need to unsafe to need to use unsafe rust. when you do the best

approach is to wrap the unsafe code in a module uh that can guarantee that the raw pointers and the foreign functions from other languages are always used correctly and that module then provides a safe API that can be used by safe client code. So really the module is extending the capabilities of the safe language. So for example, let's say we're writing a new collection type and um it it needs to call directly into the memory allocator and handle raw pointers. Okay. And whatever that resource type is that that that allocator is going to give me, I'm going to wrap that in a new Rust type and it will contain and own the pointers to objects of that type. And so

the only way to get at that pointer is through the safe API that's provided by that Rust module. Uh and that that uh raw pointer is private inside that module. Then that module can expose safe reference types to other safe Rust code. And again this comes with no uh runtime overhead because those safe reference types that are checked by the compiler compile down to just regular pointers. So it's very unusual that you you actually need to write unsafe code because of course uh the standard library has a huge variety of collection types and other abstractions just ready for you to use. When you do need to write unsafe Rust the module boundary is it's a very

clear boundary around the code that needs to be carefully audited. So okay [clears throat] let's talk about these safe pointer types. How do these guarantees work? [clears throat] Well, I'm going to explain this without using any Rust syntax. I'm going to take you on a thought experiment where we start with C and we turn it into a safe language. So, I'll begin with this question. What is the root of all evil in C? What feature of the language lets us uh write terrible bugs like use after free and corrupt memory? Well, I think we've established the answer is pointers. Pointers are very powerful. They give us full control over how we organize memory. And with great power comes great

responsibility. When we screw up, bad things happen. [clears throat] So, here's a C function that takes a pointer. Now, from reading this function declaration, what assumptions uh are we may be able or not able to make about how the function will treat this pointer? what what might we find in the comments on this function?

So perhaps the function just reads from the data. Perhaps it mutates fields of the data. Perhaps it expects that the function uh that the pointer came from Malik and it can free the memory that's returned. Perhaps it stores this pointer somewhere uh that will be accessed later after the function returns. And maybe it's even going to access that data later from another thread. Okay. And what about a function that returns a pointer? When I call get foo, I get back a pointer to a foo. Is that foo object a part of the data? Or if I call update on the data, will the foo object still be valid? um or is the foo separately allocated

and I'm supposed to free it afterwards? Well, to the compiler, these all look exactly the same. So, it has a simple answer to all these questions. [clears throat] You, the developer, are expected to read all of the API comments uh and maintain all of the interface contracts. And you're supposed to do that across all of the interacting systems in a large evolving codebase with different uh conventions, different libraries, etc. In a sense, the language is not helping you very much to get this right. And it doesn't provide a way for us to to make these uh distinctions explicit in the code. So there's an obvious simple solution to this. Remove all of the pointers. So this is where our

thought experiment language begins. We've started with C. We've removed pointers. We now have C without any pointers. And I'm also going to take away your global variables. Globals are like pointers in that they're aliased from everywhere. And it's not clear who owns anything. Okay, what do we have left? Well, we've got no pointers, no globals, only local variables. Uh, so scalers, strcts, fixed length arrays. uh and I'm also going to impose bounce checks. So any array access will do a runtime bounce check. [snorts] So without pointers everything is passed around by value. Uh return values from functions are are by value. What does this make it difficult to write? We no longer have any dynamic data

structures. The language is still juring complete, but it'll be hard to write a web browser or really anything that deals with dynamically structured input. Is it a safe language? Well, it's memory safe. I can't corrupt memory. But I think we can go further. Do we still have any types in the language that represent resources where I could uh have a bug if we attempt to use a resource after it's been invalidated? Now, can anyone think of any resource types that we still have in the language?

>> We don't have Did someone I heard mutexes? >> Files. Thank you. That is exactly the word I'm looking for. What about files? A file descriptor represents a resource. It's an open file handle. It's just an integer, but it refers to a resource that's owned by the kernel. And that resource is invalidated when the file is closed. And it's an error to use the file after it's invalidated. Now, I want to take a detour here on this talk to solve this whole class of use after invalidation problems and then we'll come back to pointers. So here we have this type called file and it contains a file descriptor and the name underscorefd is intended to imply that the file

descriptor the integer is a is a private field in that strct. So we have a a madeup API for using these files. We've got a an open function uh that returns a file. We've got a a write function that takes a character and writes it to the file. And we have a close function that that closes the file. So normal usage of this would look like something like this. Uh I call open, I get back a file, I call write. Um and then when I'm done, I can close the file. So what if I close the close the file and then write to it? Well, this compiles. I'd call it a use after close bug. What

is the feature of the language that allows this to compile? Why can I close the file but still use it afterwards? Well, there is an implicit copy that happens when I call the close function. I'm passing the close strct by value, but really what's getting passed into the function is a copy of the strruct. And I still have this variable called file afterwards. And so I can still try to write to it. For this file type, I'd like to prevent that copy so that when I pass it to close, it's gone. So, I'm going to introduce a new keyword called move. Uh, and this keyword is in all caps to bring your attention to the fact that

this is a weird thing we're adding to the language. Uh, and so I'm going to apply this move keyword to the declaration of the file type. And that means that instances of this type cannot be copied. They can only be moved around. So when I try to write my use after close bug, the compiler tracks if the file variable is invalidated because its contents are moved away. So when the file is passed to close, the file is not copied. It's moved, it's gone. So the file has been consumed by the close function and any subsequent attempt to use it will produce a compile time error. and we can't accidentally write to a closed file because the

compiler knows that the file variable is no longer available. Now, I want you to take a moment to satisfy yourself that this is a totally realistic thing for the compiler to be able to do. It just records that on the second line, the file was moved away, so it can no longer be used. Notice we didn't change any function declarations. We just added the move keyword to the file type declaration and that makes it impossible to copy file structs. Okay, what if we try to get around this? This time we're trying to duplicate the file strruct by assigning it to a variable called dupe so that we can use it after the close. But because file is

marked as a move type, this assignment is also a move. So now the file is invalid because it's been moved to dupe and we get the same error as before. Awesome. So we've prevented use after close bugs. Are there any issues with this? Anyone spot any issues? Well, you might have noticed that the write function kind of looks a lot like the close function. So if I try to call write multiple times, I have a problem. I can call write once, the file is moved into the write function parameter and I no longer have the file. So if we try to call write a second time, we [snorts] get an error. That's a bit unfortunate.

Okay. Can we change the declaration of the write function so that we can call write and still have the file afterwards? Can I pass in the file but get it back afterwards? Well, sure. Uh, we're not changing the language here, just the declaration of the of the write function. What if we make the function return the file? That would be great, right? So now we call file equals write file. file equals write file. Yeah, that would be terrible. But we have retained the property that at every point in the program there is one and only one variable that can access the file. And that's important because that's how we prevented use after close bugs. We never copy the

object. We temporarily give the file to the write function and then we get it back when the function returns. You could say the write function temporarily borrows the file object. Now, this seems like something we're going to want to do a lot. So, let's make the syntax less terrible. Let's invent a new keyword called borrow in all caps. And I use this keyword in two places. First, I use the keyword on the declaration of the write function to say that this function temporarily has uh exclusive access to the file. The file is moved into the write function and then when the function returns, we get it back again. And the second place I use the keyword

is at the call site. So it's clear that that object is accessible inside the right function. It has exclusive access to it. It can potentially mutate fields of of that object and those changes will be visible when the function returns. Now there are some rules that apply to borrowing. First borrows are exclusive. So that means that while a variable is borrowed, it can't be accessed. You can only have one borrow at a time on an object. And in this example, I'm calling a a swap function that takes two borrowed files and I'm trying to pass the same file for both parameters. So when I say borrow file the first time, the compiler is tracking that the file

is now borrowed. And when I try to say borrow file again for the second parameter, well, it's not available to be borrowed. we can't do that. So this doesn't compile. Um and so this shows us that borrows are exclusive means that a variable can only have one borrow at a time. Okay. The second rule is that borrows must be given back. If you have a borrowed object, then you can't take ownership of it and move it away. You don't own it. You're just borrowing it. So in this example, I have a borrowed file and I'm trying to move it into the close function. But calling close requires that I give ownership of the file to

this close function. And I I can't do that. So if I could take ownership of it here, there would no longer be a valid file to give back to the caller when we return. However, when you have a borrowed file, you have exclusive access to it. So you can mutate it. You can even swap out the whole object replacing it with another instance of the same type. So in this example, I have a borrowed file and I create another file called temp. Then I swap the two objects. So now temp contains the original file that was passed in and I can close it. When I return the variable that the caller passed in now contains the new file that

I opened. So the point here is that when you have a borrowed object, you have exclusive access to it and you can mutate any part of it or even the whole thing. Okay. And lastly, borrows are for function inputs only. We know what it means for a function parameter to be borrowed. The borrow begins when the function is called and it ends when the function returns. We don't know what it would mean for this function to return a borrowed file. In other words, the borrow ends at exactly the moment that the function returns. This means that the the file cannot escape. The borrower cannot escape from the function. Uh it also can't be stored somewhere that could uh

continue to exist after the function returns. Now a little foreshadowing. This is a limitation that we will revisit. But for now, this is this is all we know. Okay, an aside. We invented these borrowed references or borrowed types for move only types, but it's useful for types that can be copied as well. Integers are copyable. Here's a function that takes a borrowed integer and [snorts] it increments it after I've called increment twice. Of course, the counter's value will be two. And you're probably thinking, yes, we've just reinvented pass by reference. When we pass a borrowed parameter, we don't actually have to move anything around in memory. This can all be implemented by just passing a pointer to the existing

location of that variable at the call site. So a borrow is actually just a pointer type, but it's safe because these references cannot alias, meaning you cannot have multiple names for the same object in memory. And these references cannot escape. They cannot be returned or stored. So you can't end up with a a dangling reference or a dangling pointer to something that's no longer there. Okay, let's take a minute to review what we've done so far before we add any new concepts. We started with C and we turned it into a safe but fairly useless language by completely removing all of the pointers. Then we took it a step further. We said, well, even without pointers, some types

are like references. Uh, sorry, some types represent resources. uh and allowing those types to implicitly be copied leaves us open to use after the invalidation bugs uh like where you could close a file but still pass it to the right function afterwards. So to fix that we invented types uh that are move only with the move keyword. So now when you pass file to close, it isn't copied, it's moved. And the close function consumes the file and the compiler understands that you no longer have it. Then we realized we don't want the write function to consume the file. So we invented the idea of borrowing with the borrow keyword to temporarily give the function exclusive

access to that object. And it turns out that a borrow is really just a reference type. It's a pointer. So we have this this new reference type and we have a distinction between types that are copyable and types that are move only. So let's run through some examples and make sure we understand which column they fall into. Let's start with int. Are integers copyable? Yes, sure. It's just uh you know all scalar types are are copyable. This is just plain old data. It's a value type. It's fine to copy them. It doesn't represent a resource. It's just a value. What about our file type? Is that copyable? No. We've declared it with the move keyword

because it owns a file descriptor. It represents a resource. We want to make sure that there's always only one instance responsible for that resource. How about other strcts? Here's a strct uh containing x and y coordinates for a point. Should that be copyable? Sure. Yes. It's just a value type. it it doesn't have any responsibility for any kind of resources. Uh so copying it won't cause any problems. Uh and this type is not declared with the move keyword. So it's copyable. What about borrow file? Now first let's be clear. Borrow file is a distinct type. It's a pointer. We call it a borrowed reference to a file. It's just a pointer with some compile time

constraints. So far, we've only seen this type used as a function parameter, but we'll come back to that. So, should borrow file be copyable? Well, it's a pointer that has exclusive access to an object in memory. So, should I be able to make another copy of that same pointer? No way. Because if I did that, then uh there would be two pointers each claiming to have exclusive access. So, borrows are exclusive. So borrowed references are not copyable. They are move only. Lastly, what about borrow int? Borrow int is also a distinct type. It's a pointer to an integer. Should that be copyable? Integers are copyable. But should a pointer that claims to have exclusive access to a particular integer be

copyable? No. Just like a borrowed file, a borrowed reference to an integer is also a move only type so that it continues to have exclusive access to the thing it points to. Borrows are always exclusive. So all borrowed references are move only. So you can see how we now have two concepts moveon types and borrowed references working together to maintain this this guarantee of exclusive access. Okay, this brings us to the end of the detour. We've solved use after close bugs by wrapping the file descriptor in a in a move only type that can't be copied. And we've invented borrowed reference types that make it easy to work with move only types. A file is a resource. Heap memory

is also a resource. Can we do the same thing with heap allocations? Can we bring them back into the language while preventing use after free bugs? Well, this is what we want to make safe. This is this is regular C with pointers. This is not our thought experiment language right now. So, we malic some memory. We work with it and then we when we're finished uh we free the allocation. Malo and free look a lot like open and close. and the data pointer is holding the resource the heap allocation. Okay. And what we want to prevent would be if we continue to use the resource after it's been invalidated by calling free. So the solution here is to

introduce some library code with a type to manage a heap allocated array. Now this example is an array of integers. Of course Rust has generic collections that can uh handle items of any type. uh and it also has other kinds of collections like hashmaps and and b trees etc. And internally this this collection type uses unsafe code uh it has a raw pointer. So the underscores on the field names here are to suggest that again these are these are private fields that can only be uh accessed within the implementation of this types methods. So the fields we have are the pointer the pointer to the heap allocation. We have a length uh which is the number of items

currently actually in the array and we have a capacity which is how big is the the heap allocation and how much total do we have space for. And hopefully this is a fairly uh familiar layout for a uh for a dynamic array. [snorts] The strategy would be here um as I append items to the array when I reach the capacity then I might double or you know somehow scale up the uh the the size of the heap allocation um so that I don't have to reallocate on on every uh append to the array. So this type is going to uh provide a safe interface that cannot be misused by other code. uh and it's declared with

the move keyword because this type owns a resource. Okay, let's take a look at the the API for this thing. So, we've got a a new array function that constructs an instance of an empty array. We've got a destroy function that takes the array and consumes it and frees the memory. Now, I should say here that um I'm making destroy explicit for these slides, but of course in actual Rust, we have destructors just like in C++. So this would be cleaned up as soon as it goes out of scope. Okay. So so far this is all working great just like open and close with a file. What about a push function to append an item to the array?

So this is just like the write function on our file. So we have a borrowed reference to the array. So the push function has exclusive access to the the content of the uh the array strct and we get uh a uh an integer an item that we're going to append to the array. So uh the push function can do its work. It can check if the capacity has can be has been reached. It can reallocate to make more space if it needs to and then it can append the new item. So the borrowed reference type is giving push exactly what it needs to do its job. And when push returns, of course, the calling code can continue to use the

array. Okay, what about accessing elements in the array? So, I'd like to write a get function that takes a borrowed reference to the array and an index for the the item that I want to get at in the array. But I don't just want it to return a copy of that element. I would like a pointer to that item in place in the array. First of all, because I want this to work with types that cannot be copied. We know that we have many types in in the language that cannot be copied that are move only. Um, and second, because I want to be able to mutate that object there where it is. Right now, I don't have a type that I

can return from this function to do that. What if this function could return a borrow? That would be ideal, right? If we could have a borrowed reference to the item in the array, I'd have exclusive access to it. I could read it. I could mutate it uh in place without copying it. So, to make this safe, let's review the rules for borrowing. First, we said borrows are exclusive. Well, that's a a natural consequence of the concept of borrowing. You have exclusive access to the thing that you're borrowing. Second, we said borrows must be given back. Again, that's a natural consequence of the idea of borrowing. You have to give it back some time so that the calling code can continue to

use that that object. And last, we said borrows are for function inputs only. In other words, the borrow ends at exactly the moment that the function returns. And that was just because that's all we knew how to do. So what if we could extend the borrow for a while after the function returns? What if we could return a borrowed reference? Okay, we're going to need some constraints on this to make it safe. So to do that, we're going to introduce the concept of regions. So a region is like just a region of a function from line five to line 10 uh in whatever that function is. So when we call get on the array and we get back a

borrowed reference to an item in the array, we'll be able to use that borrowed reference that we got back for some region within the function. Um, and the borrower of the array is extended beyond the return of that get function. So while the borrow persists, we enforce some special rules to make sure it's safe. So to get clear on those on those rules, let's see what we're trying to prevent. Okay, so we make a new array and we push an item uh with the value 10 and then we call get with index zero and we get back a borrowed reference to the item that we just put in the array. So x is a pointer

into the array. Then if we call destroy on the array, x is pointing into an object that is now invalidated. So at that point if we were allowed to dreference X that would be a use after freebug that could corrupt memory etc. That would be bad. So we can't let that happen. What we do want to have work is that after calling get we can work with X. We can dreference it. We can mutate the items value etc. And then only when we're finished using X can we do something else with the array like destroying it. So the compiler is going to track the region of this function where x is in use. A region as I say is like a a range of

lines of code within a function. Of course it's more fine grain than that but that's the concept I want to give you. The borrow starts with when we say borrow array and it lasts for as long as x is in use and within this region of the function array is considered to be borrowed. What if I destroy the array and then try to increment X? Well, the region of the borrow lasts for as long as X is in use. And now the attempt to destroy the array falls within that region where array continues to be borrowed. And because array is borrowed, it's not available to be accessed at all. You can't touch it because it's still borrowed by X. So we

get a compiler error. Array is borrowed. Okay, this is really cool. We took what we learned from preventing use after use after close bugs with files and we've used it to prevent use after free bugs with dynamically allocated memory. So we have this safe pointer type, this borrowed reference that we can return from a function and functions can safely return pointers into the insides of objects. Um, so this enables us to write collections that uh that have a safe API and that can hand out pointers to the items they contain that we can work with and mutate in place. And this comes with zero runtime overhead. This all just compiles down to regular pointer code

because the safety is constructed and checked at compile time. And this is extremely useful in all kinds of situations, not just in collection types. Are there any limits to this? What if I want to access multiple elements at once? Will this code compile? Can I call get twice and hold pointers to both items at once? Well, no. Because when I call get the first time, array is borrowed for as long as x is in use. And if I try to call get again, well, I can't borrow array again. Borrows are exclusive. So I get an error. Array is already borrowed. Now, this is actually correct because what if I passed in the same index both

times? If this was allowed, then both X and Y would point to the same item. They would both be claiming to have exclusive access to the same integer in memory. So what if I don't actually need exclusive access to each item? What if I just want I I should say actually before I move on, the the compiler can't check that uh you know, oh, you've passed in array index zero for the first call to get and index one for the second call to get. uh it just knows that you're borrowing array and you're returning a reference from get and for as long as x is in use that array continues to be considered borrowing. [snorts] The compiler is not

going to look inside the get function and figure out oh yes these items are disjoint or any of that. There's no crazy like whole program analysis going on. This is actually a really simple fairly straightforward rule for the compiler to to check uh around array [clears throat] continuing to be borrowed while we have x. Okay, what if I don't actually need exclusive access to each item? What if I just want readonly access to multiple items in the array? Well, for that we're going to introduce a new concept called sharing. Now sharing is like borrowing but it's weaker. So when something is shared it's locked in a readonly state and no one can mutate it. And because it

cannot be mutated aliasing is fine. You can have as many readonly references as you like. Safe in the knowledge that nobody can change the object out from underneath you. Nobody can invalidate it while you're holding that reference. So a simple example here's an add function that takes two shared references to integers and again we're inventing a new keyword. The share keyword is like borrow but a shared reference does not have exclusive access. The object is borrowed in a readonly sharing kind of way. So to use this function if I have two variables A and B I can call add with share A and share B. I can also call add with share a and another

share of a for as long as v as a variable is shared it is only available for reading and more sharing and in this case add doesn't return a reference so the sharing ends when the add function returns but we can also return a shared reference from a function just like we did with borrow so now we can write a get readonly function that takes a shared array Now I've called it get_ro there because yesterday uh I I came and I sat about halfway up those stairs and I couldn't read anything on the slides. So I went home and made my font twice as big everywhere. So I've crammed it in. Um so get arro is get read only.

So this is just like the uh the the get function but it uses a non-exclusive shared reference instead of exclusive borrowed references. So using this function now I can have read access to multiple items in the array. So I start by constructing an empty array. I push the values 10 and 20 into the array. I call get read only the first time with index zero and I get back a shared reference to an integer x. I call get readon again uh again with a shared reference to the array which is now locked in this readonly shared state. I get back another shared reference to a different item or it could be the same item. It's fine. Uh

and I call that y. Then I can call my add function with these two shared references and the array continues to be shared for as long as x or y are in use. So when I call share array the when I say share array the first time, the compiler begins tracking that the array is in this shared readonly state. And when something is shared, you can only read from it. Uh so you cannot have exclusive access to it. You can only borrow it. What I can do is share it some more. So I can say share array again for the second call to get read only. And the array remains in this shared only state

until X and Y are no longer in use after I've called add. Uh and only then can I destroy the array. So this all works great. What if I try to borrow while sharing? I'm going to swap the order here. So now will this compile? Well, the region in which array is shared looks like this. And inside that region, I'm trying to say borrow array so that I can call push. But the [snorts] compiler knows that array is shared within this region. So I cannot borrow it. I can't have exclusive access to it. So I get an error here telling me I can't borrow the array because it's already shared. Okay, here's an interesting question. If

we allowed this to compile, what terrible thing could happen at runtime?

>> Say again. Exactly. The array could be reallocated. Thank you. The push could reallocate the array. This could be a use after freebug. So here's the array storage allocated on the heap. And if it's at capacity, then the call to push will reallocate and move the array elements to the new location. But x is still pointing to the old location. So when x is dreferenced, we're actually accessing whatever might have been allocated there in the meantime. And this is a potential security vulnerability. Now, this exact thing happens all the time in C++ with iterator invalidation bugs that can blow up at runtime. And if you've ever read the iterator invalidation rules for C++, they're not

simple. In Rust, we have safe pointers with borrowing rules that catch these bugs at compile time. So with move semantics, regions, borrowing and sharing, we have an API for a heap allocated array that is completely memory safe. It cannot cause memory corruption because the compiler checks these constraints on our safe pointer types. And the key constraint is that you cannot mutate while it's shared. Now this is a key insight of Rust's ownership system. It's okay to mutate if you have exclusive access. It's okay to alias or share if nobody can mutate, but not both at once. This principle is often referred to as mutable exor shared in the Rust community. The compiler is enforcing the

reader writer pattern at compile time. At any given moment, you can either have one or many readers or a single writer. So if I have a variable X, we can take a shared reference to it. And while it's shared, we can only read from it or make more shared references to it. And only once all those shared references have ended can we then borrow X or mutate X because we have exclusive access to it. And only when X is no longer shared or borrowed can we destroy X or move it away. And these rules are enforced at compile time. This is all based on checking one function at a time. We don't need to do any whole program

analysis and there's no runtime overhead. This compiles down to just regular pointers. And we invented all this to make correct singlethreaded programs. But it turns out that the resulting system is completely type- safe because the definition of a database is two threads accessing the same location. At least one of them is mutating with insufficient synchronization. That requires mutation and sharing which we just made impossible. Tada! Thread safety. Okay, now at this point you might be thinking, "But hold on, Ben. What if I want to share some state between my threads and I want to mutate that state?" Well, borrowing and sharing give us the basic building blocks from which we can build a huge variety of APIs that

extend the capabilities of the safe language. So for example, the Rust standard library provides a mutx that can be shared between threads. [snorts] So I'm taking the data that I want to protect with the mutex and I'm putting it inside the mutx. The mutex is like a collection of size one. So it's only possible to access that data by going through the mutex API. The mutx is shared. It's aliased because it's accessible from multiple threads. That means it can only be accessed through a shared reference. In other words, when I give out references to multiple threads to be able to access this mutex, the only thing I can give them is something that amounts to a shared reference.

So it seems like mutx is stuck in this readonly state but the mutx has a method that takes a shared reference called lock. So either of these threads can call the lock method and internally lock calls pthread mutx lock or whatever the appropriate function is on that platform to block until it can have exclusive access to the mutex and then it returns a guard object. Now the guard object is a resource representing the fact that we are now holding a lock on the mutex and because we have exclusive access the guard can actually give us it can act as a smart pointer that gives us a borrowed reference to the data inside the mutex. So now the thread that called lock has

exclusive access. it can read and write the data and when it's finished and the guard is destroyed the lock is released and other threads can take their turn at that point. So because the lock function enforces exclusive access at runtime this is safe. It's not possible to misuse mutx and cause a data race. The compiler just won't let you. Mutex is just one example. There are readwrite locks channels uh atomics work steal and cues all kinds of options. They're all built in library code using these basic ideas of ownership. So with move only types that can't be copied, we can write a write function that consumes an object. We can write a function that consumes an object knowing

it can't be used again after it's been invalidated. Uh with borrowing and sharing, we got back pointers that are safe because they enforce mutable exor shared. And with regions, we can safely return those reference types from functions with the input continuing to be borrowed or shared as long as that returned reference is in use. And borrowed references and shared references generate the same code as regular pointers, but the compiler treats them very differently. So now we can write APIs that specify the constraints that apply to the pointers involved, and the compiler can check these constraints for us. And this removes a huge burden compared to maintaining C or C++ code where you're supposed to read those comments and

remember, oh yes, I I need to make sure that this is alive only while that's being hold all of the things together. Now, I haven't shown you any actual Rust syntax. Of course, does Rust doesn't have any weird all caps keywords. Uh it does have a very modern type system. It has great package management. It has really helpful compiler errors that help you learn the language. It does away with debugging seg faults and memory corruption at compile time. And uh from experience I can say that it's very practical for for large projects and the end result is a greater sense of security that our software is more robust because so much more safety is constructed at compile time.

Now, of course, con catching memory bugs at compile time means producing new interesting compiler error messages when we write code that breaks the rules. Coming from other languages, these uh these errors will be unfamiliar. And to really understand the ownership system, you need some experience bumping into those errors and fixing them. People often talk about a period of fighting the borrow checker. That the borrow checker is the phase of the compiler that checks the borrowing rules. Once you've internalized the rules the compiler is playing by, you'll run into them way less often. You'll be designing ahead to work with the ownership system and Rust's error messages really are very helpful in this process. They teach

you as you go. Of course, to get that experience, you'll need to start learning actual Rust syntax. So uh I've uh I've started a YouTube channel um where I will be publishing a complete uh course in Rust freely available and the tagline of Rust curious is Rust explained carefully. My goal is to make the highest quality available video learning materials. It's called Rust Curious because I don't assume that my audience has already decided to learn the language thoroughly. I want to get you as quickly as possible to the point where you can have that experience of playing with the language, building your own experiments, spawning threads, running into borrowing errors, and seeing how in practice it

works out that the language is protecting you uh from data races and all the other nasties. So, if that's of interest to you, please subscribe to the channel. I'll also be running a parallel track of videos that are offered for purchase um that go into more depth into the the practice of using Rust for real. Uh and so um I'm also available for uh for inerson training workshops. So please email me if that's of interest. I'll be uh in the hardware village right after this. I have some soldering to uh to catch up on. So if you'd like to uh if you'd like to to chat, please come and say hello. Um, thank you for your

attention today. I think really Rust prevents sorry, Rust presents an opportunity to significantly increase the the quality and the robustness of the software that that we build. And so I hope you find that it's an interesting and and a useful language to explore. Thanks very much. [applause]

Great talk. Do we have any questions for Ben?

>> Yep. >> Thanks, Ben. Great talk. Um, look, I got to ask, given that you worked so long at Apple, what do you think of Swift? >> Sorry, I can't quite see where the question is coming from, but I'm really focused. Ah, great here. Great. Thank you. Uh, yeah, it's true. I worked at Apple. I've worked in Swift. Um, Swift is a is a different language that um uh that shares some background but very different background in other ways different goals. Um, it's uh I I see the two as potentially very complimentary. >> If there's one last question. >> Well, let's thank Ben one more time. [applause]