Stanford Seminar The Rust Programming Language - The Best Documentary Ever

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
thank you so much for the intro yeah so I'm here in Tehran I'm an employee at Mozilla research and I'm really excited to tell you about our rust project which is you know been an ongoing research project for the last sort of four or five years in terms of having a lot of manpower behind it and is just on the cusp of actually being introduced in 1.0 sort of to the world to use as a more serious product so so my background is in programming languages I did research in programming languages and it's it's kind of an interesting time to be a person working in programming languages because for for a long time it had been considered a sort of dead area everybody was happy with C or Java or what-have-you but in the last few years there's been this kind of resurgence of interest in new programming languages with lots of different industry groups coming out with languages including Facebook and of course Mozilla so with all these languages coming out you might ask okay what you know what is sort of special about rust why rust why is Mozilla building rust so the the kind of headline for rust is safe systems programming I'm gonna explain what I mean by that in the next few slides but in terms of sort of the the lineage going into rust you have traditional systems programming languages like C and C++ which I'm sure all of you are familiar with and then languages that might be a little less familiar like ml and Haskell actually I'm curious are who in the room is familiar with either of those languages okay so quite a few people that's great so rust is sort of trying to blend the best of both of these languages to get at this problem safe systems programming okay so so one question you might wonder is why is Mozilla interested in creating a programming language so you probably all of you know that Mozilla is the organization that created the Firefox web browser which is currently written in C++ and it turns out actually that web browsers have a sort of need for a combination of things that are in deep tension so on the one hand web browsers need a high degree of control over the machine there they're sort of asked to do very complex tasks very quickly they're asked to display media they're asked to have you know all of these tabs running simultaneously running code that's untrusted you know that's been downloaded from the internet and then on the other hand they need safety because they're running this untrusted code from the internet right and everybody's out there trying to break into your machine and these two are sort of at odds because languages like C and C++ that give you the kind of control you need to get the performance for a competitive browser also leave you open to all kinds of security vulnerabilities right and so you're constantly hearing this you know Firefox has been patched or Chrome has been patched you know somebody can break into your machine through a website so the idea is we want to build the next generation web browser which is a project called servo and we want to do it in a new language that tries to do better at giving you safety and control at the same time okay and so rest and server are kind of the symbiotic pair of research projects okay so that's that's the really high-level picture and I want to spend a little bit of time going into more detail about exactly what I mean by control and what I mean by safety so let's start with control so C++ is is sort of the best existing language in this respect and even in just a small snippet of code we can start to see the kind of control that you get over the machine so for example if we declare a vector in C++ we know exactly how that's going to be laid out in terms of you know the the memory so in particular there are some fields of the vector including a pointer to the actual data and some metadata about it that all live on the stack and it's very important that that data is allocated on the stack and then there's a pointer that goes directly to the heap on and in this particular case where we have a vector that's containing a string and we sort of have the same pattern again where the string is itself another vector and the relevant sort of pointer and metadata is stored in line in the array so to actually get a piece of string data you follow these two in directions one to get into the the vector itself on the on the heap and then one more to get into the string and we'll contrast that with some other languages in a second but the the high level point here is you have you know exactly what things are going to look like in memory and you you have a lot of control over that layout in addition C++ gives you various kinds of pointers including things like these lightweight references which can point in to the heap and they can point into the stack and there's there's no overhead for doing so you have sort of all of the power you're normally used to with pointers and then finally there's something implicit here which if you know C++ is sort of obvious um so at the end of this function the vector goes out of scope and as a result its destructor is run and its memory is freed you didn't have to say anything for that to happen so that's a nice convenience but on the other hand you know exactly when it's going to happen by reasoning about the scope so that's in contrast to something like garbage collection where the data might be freed at some point in the future but you don't know exactly when you can't control it it might be inconvenient okay one of the principles that you get out of C++ is something often called zero cost abstraction and what this means is that you can build up libraries like vectors and strings that are reasonably convenient to use they give you a nice abstraction but when you compile it down it's nothing different than you know something you could have written by hand in assembly you're sort of not giving up any performance in doing this okay now if you look at a language that is safe but doesn't give you control like Java this story is very different so in Java if we tried to sort of set up the same scenario where we have a vector containing strings we have layers and layers of indirection because Java enforces a kind of uniform representation on everything it doesn't give you control over stack layout versus he play out and so on and so forth right and so in Java any abstraction comes at a cost and I want to I want to be clear here I've been focusing on memory layout as a relatively simple example but this applies to many many things so being able to choose between static dispatch and dynamic dispatch again in Java basically everything is dynamically dispatch everything going through objects in C++ you have the choice template expansion is related to that and there are there are many more examples so what we what we want is this to retain this notion of zero cost abstractions and control from C++ but to somehow get the kind of safety that languages like Java and the Haskell and so on provide ok so so now what do I mean by safety so let's go back to this example in C++ where we've we've set up a vector we have a pointer into that vector and let's let's just say for the sake of argument the vector has one element and a capacity of one right now if we then try to push an element onto the vector well it's out of capacity so it's going to have to allocate a new chunk of memory and copy over the data and at that point it'll swing the data pointer right to that new location on the heap problem is we're left here with the existing reference elem to the old location on the heap right that hasn't been updated so this is the classic dangling pointer problem and of course if you then go on to dereference that pointer bad things can happen people can break into your machine right this is this is the source of security exploits like a very simple example ok so I want to sort of take a closer look at exactly what went wrong here what are the ingredients we need to create this kind of security problem so basically I want to say we need two things happening at the same time one of them is aliasing that is we have two pointers to the same or multiple pointers to the same memory location all right so we have the data pointer which was being updated and then this Allen pointer which sort of didn't know about the update that happened so aliasing is one thing and then mutation is the other right if if you weren't allowed to change the data then this problem wouldn't happen so it's this combination of having multiple pointers and being able to change the data under under those pointers that can cause things to get out of sync and then you can read unallocated memory and by the way please ask questions throughout are there any questions up to this point yeah architectures right now have you implemented your language ah so the Brust uses LLVM as a back-end so basically it supports all of the architectures that LLVM does we on the other hand you know sort of that's that's the in principle answer you know there's there's a lot of work to make different things like the standard library work on on those architectures but we work on you know x86 machines we work on arm power and and a few others I mean it's mostly major architectures at this point yeah so depending on the safety is opium LVM makes transformations on the edge correct syntax tree eventually walks generate code yeah produces various optimizations it makes assumptions about the safety of those guys right so that's that's a that's a really great question um so you know one interesting thing about targeting LVM is that we inherit a lot from C and C++ because that's sort of where LVM is targeted and as we'll see later in the talk in C and C++ there's this notion of undefined behavior something like dereferencing a pointer like this and part of the idea of undefined behaviors it's a contract between you and the compiler if you do something like this the compiler is free to optimize it in crazy ways I mean basically you have no guarantees about the semantics of your program right on the other hand if you don't have undefined behavior everything is supposed to work out hunky-dory right and so part of part of the idea with rust is that it gives you a lot of tools where basically if you if you follow the discipline I'll be showing you in rust and the compiler enforces that then you never introduce undefined behavior right it's only if you sort of break out of the rules right so I'll spend about half of the talk talking about concurrency any other questions all right so one thing you might be wondering is you know doesn't garbage collection solve certainly the safety problem well as I sort of already mentioned garbage collection gives up a lot of control in particular you you lose that deterministic destruction what resources are free and it also requires a runtime which is a sort of substantial abstraction penalty that you're paying but there's sort of a more interesting reason that garbage collection isn't the whole answer here which is that you know garbage collection solves a certain category of problems like memory safety problems like the dangling pointer problem I showed you but there are many other classes of bugs that garbage collection doesn't give you any help with such as iterator invalidation where you have a piece of code that's iterating over some data structure and makes a call that updates that data structure and therefore sort of invalidating the iterator right that's garbage collection is completely orthogonal to that and there's also something called data races which come up with concurrency and I'll spend a while talking about that later so I just want to say garbage collection is even if you were willing to give up some control it still doesn't solve all the problems you might want to solve but rust will actually help you with with all of these problems okay so that's that's the problem space those are the goals so what is rust so at heart rust is about ownership there's there's a discipline built deep into the type system that enforces the notion of ownership and a sort of related notion of borrowing so the idea is that every resource in the language always has an owner at all times and by resource I I mean things like a vector but I also mean things like a file or a lock it can it can mean a wide variety of things when once you have some resource you can lend it out on a sort of temporary basis that's that's what we call borrowing and when a resource is borrowed then it you know certain restrictions apply so for example it might not be mutable or it certainly can't be freed until the owner actually says I'm done with the object okay that that's ownership but a very high level I'm gonna be explaining this at sort of increasing levels of detail but that's that's the the core solution so sort of in graphical form how does this help well basically it solves all of the problems that I've mentioned you don't need a runtime to follow this discipline it's totally done at compile time in the type system so that puts you sort of in the ballpark of a language like C++ giving you the control that you want it you get memory safety which was one of the benefits of garbage collection but then you also get data race freedom which there aren't really mainstream techniques that give you that kind of guarantee and again I'll come back and explain exactly what I mean by data race freedom but it has to do with concurrency bugs okay and then I just want to briefly say you know I'm gonna spend the rest of the talk basically focusing on this notion of ownership and borrowing because that is really the heart of rust but the fact that rust is as successful as it has been sort of as an early project it has to do with a lot of other things as well so rust feels like a very modern language of inherit it borrows a lot of ideas from languages like ml and Haskell that make it feel very nice to program and despite the fact that you're sort of dealing at this low level of abstraction and exercising this kind of control and then finally I have to I just have to give credit to the open source community around rust part I want to introduce this slide both to give credit but also to say to invite all of you to join this community it's very open you know very welcoming if if you're interested in this stuff it's very easy to get plugged in ok so now let's actually dive into some of the details so I'm gonna start by talking about the core concept of ownership which is very simple and then we'll move on to borrowing ok so at the stick-figure level I imagine you have some resource like a book so as I said these resources always have a clear owner at all times so right now we have an owner on the left ownership can be transferred to a different party and at that point the original owner is irrelevant so you can think of owners here as threads you can think of them as functions pieces of code but basically ownership transfer makes the original owner irrelevant and when the final owner says I'm I'm done with this resource the resource is destroyed in the same way that the you know we saw the vector example before the destructor is run so it's only when the final owner just drops it on the floor that the destructor is run just like it is in C++ okay that's it very very simple discipline so in terms of you know remember we looked at this problem of memory on safety and said you sort of need these two things aliasing and mutation at the same time to cause this problem so ownership chooses basically to forbid aliasing there's always exactly one owner there's basically only one alias that can be used at any time for an owned resource but the owner can do anything they like to the object so they can certainly mutate it all right that's the stick figure version now let's see the same thing with some code yes then you actually have limitations right again great question that you keep sort of looking forward ahead to some stuff I'll cover later in the talk so yes I mean this is certainly this basic ownership discipline before we get to borrowing is way too restrictive to to write many algorithms it's actually surprising it gets you surprisingly far for programming applications that just use libraries of existing data structures and so on but if you wanted to implement a linked lists you'd be in trouble with this approach right and that's a pretty poor data structure so I'm gonna come back to this point there are various ways to bend the rules but it's it's all carefully encapsulated other other questions before we move on okay so I want to come back now this is an example and now we're looking at Russ but it's sort of similar to what we saw with C++ earlier so we you know introduce a vector it's laid out on the stack exactly as it was in C++ when when we create the vector it's owned by this piece of code on the left so that owner can go ahead and mutate it it can push elements onto the vector that's totally fine and then at some point it can pass that vector to another function and notice this function take it is just writing the type Veck directly no no sort of incantations or anything else by default if you if you just write a plain type what you're saying is that when you pass me this argument you are actually passing ownership of that value okay so this borrowing stuff is is something you will annotate as we'll see later but by default if you if you don't say anything else you're transferring ownership and so that means after this function call to take the original function give no longer has ownership of the vector basically it's completely irrelevant okay and in terms of what this actually looks like sort of at runtime when you make the call right you're gonna copy the stack data into the stack frame of take and then at that point the original stack data is still there but it's sort of unusable and the type system is going to enforce that as we'll see in a second then take execute and here's a key point if take didn't pass ownership of vector somewhere else we're back into the same situation we saw with the original C++ code at the end it's going to free the vector and so now you have what looks like a dangling pointer here but it's grayed out okay and I'll show you I'll show you what that means that they compile all right so you might worry what what's to prevent give now from going on and using this vector which has been D allocated so this is where the type system actually comes in and does some checking for you the type system understands that you've transferred ownership of the vector after this call and so if you then try to use the vector in any way say by pushing more elements you'll get a compiler that says this vector has been moved you don't own it anymore you don't have access to it so even though even though there's a dangling pointer in the stack frame it's not usable so it's not causing any trouble alright that's that's kind of the point here and eventually that stack frame will be popped of course okay and this this kind of checking you know certainly covers basic errors like use after free as we just saw or double moves where you're claiming to transfer ownership to multiple places and we'll see some more examples of other things that it addresses later on any questions about just the basic ownership discipline so there's a notion of what we call copy types so things scalar values like integers are considered copy and what that means is that when you do something like this this function call where it looks like you're moving ownership you still retain access to the original value because basically you're saying that you've just copied the value when when passing it in some sense and in particular that the so the compiler checks whether copy is a valid thing to apply to a type and you can't use it for types that involve the heap in any way so it's always shot shallow scalar types but yeah if you had to follow this discipline for just basic programming with integers you'd be in trouble integers are not resources right there they're just you know values that you can easily copy around anything else okay so now let's look at the other aspect of the ownership system which is borrowing now there are two forms of borrowing and rust and we'll look at both of them in turn so the first is what we call shared borrowing and it's a little hard to imagine the right physical metaphor here but it's as if the original owner is able to simultaneously give access to a resource to some shared group of other parties and for that to solve the the safety problems of course we've created aliasing therefore we must rule out mutation well this well this is happening right so you can share pointers to this thing with all of your friends but none of them are allowed to actually mutate the data through their aliases okay then in contrast there's also a notion of mutable borrowing and in this case you're sort of lending it out to one party who could for example subsequently lend it out to another party but there's still only one borrower active at a time right so here we're ruling out the aliasing but allowing mutation and unlike the ownership transfer such situation I described before the original owner is sort of still important here there's a stack discipline basically where ownership is getting returned from Party to party until it goes back to the original owner okay right and as I said this this approach prevents aliasing and allows mutation okay so that's the stick figure version here's the code version I'll start with shared borrowing okay so the code on the left is pretty similar to what we saw before really the only difference is that there's now this ampersand in front of the Veck and the code on the right has an ampersand in front of the type right and that as as we saw in the previous slide that designates a shared borrow right so initially the code on the left owns the vector it can we take the vector push pushing some contents and then it hands out a borrow of the vector to the code on the right or shared a shared reference what this looks like at runtime is a little different than what we saw before because these references are just pointers right so this is also referenced in this sort of C++ sense okay and then the function on the right can do whatever it does but unlike the case we saw before when it finishes right it's relinquishing ownership not of the vector but of the borrow it's saying basically the the loan has expired and so that pops you know the reference off the stack but the original vector is still available and that means that the original code could go on to to mutate it or transfer ownership or do whatever else okay now remember I said for this to actually solve the safety problems you can't allow mutation while this is going on so there are certain things you won't be allowed to write in the function and the use function right you can't push things on to the vector you can't mutate the contents of the vector basically it's it's a frozen object from your perspective you can read from it but you cannot modify it in any way and that's all again enforced at compile time by rusts type system okay and I'll put an asterisk here because you as I said you can bend the rules in various ways we'll see that a little bit more later okay so that's that was shared references shared borrowing here's an example with mutable references so this is a function that's just pushing all the contents of one vector onto another so the source vector is taken as an immutable borrow because it doesn't need to be updated and the target fact vector is taking as a mutable borrow and then internally you are allowed excuse me to call mutable methods like push on the target vector because it's mutable borrow and I'm gonna skip the details here but basically although you're writing this for loop at what seems like a high level using these iterators and so on it compiles down to same kind of code that C++ gives you so there's some zero cost abstraction going on here but that's not super important right so in terms of what's going on and the layout as you're iterating over this vector you're getting references onto the heap in the form of this LM and then you're you're pushing copies of those references over onto the other vector [Music] okay now here's an interesting question one thing that can go wrong with this kind of code is if you happen to pass the same vector as source and destination all right let's step through and see exactly what happened so so suppose we've started the loop right we have an LM pointer like this and the next thing we're going to do is push that onto the vector but now we have the same kind of problem we had before if the vector is out of capacity that's going to involve allocating some new memory and copying over the contents and now we have a dangling pointer again right and so if we continue iterating we're going to try to read a dangling pointer we're going to have the same kind of safety violation they are they are by value but these you know a parameter like this is taking a reference right so it's a little hard to answer to that question I guess so you you can think of it as by value but then you have reference types right yeah yeah okay so so it would be bad if for us to love this but it doesn't so how is this ruled out well we have this this function signature right that says it's taking on the one hand a shared borrow of a vector and on the other hand a mutable borrow of a vector right and then at the call site we're trying we're taking this borrow right ampersand you can think of as a sort of request a loan right request to borrow this value and the problem is that this we can't have both of these Burroughs simultaneously so rust is actually tracking which values have been borrowed and what and it can see that you're trying to borrow the same vector as a shared borrow and immutable borrow which would introduce aliasing and mutation and so you get an error that's ruled out okay so this is another kind of bug that rusts can catch for you another way of thinking about this is that when you have your hands on an ampersand mute value that is the only active alias of that of the underlying memory at that point all right that's the only way to do anything just to spell this out it's a little more graphically in an example like this when we write ampersand back in that let LM line that introduces a borrow that lasts for the whole scope so if we try to push later on in that scope we get the same kind of error we got before but in the previous example that vector is burrowed you can't try to meet you can't mutate it right but once the scope ends the loan is complete and then you can go on and push on the vector all right so there's this kind of stack discipline to borrowing and this fine-grained tracking of which values have been borrowed that lets rust catch these kinds of bugs okay so that's the end of the basic ownership and borrowing story I'm going to talk about concurrency next but before I switch topics I just want to make sure this part was clear absolutely and that's something we're not taking as much advantage of right now as we could because we just used the LLVM tool chain and it doesn't it doesn't know this stuff so there we can communicate some of it but I think there's a lot of avenue for actually getting faster than C++ this way because you know so much more yeah yeah there questions comments okay so now I want to switch gears and talk about concurrency which is another enormous source of bugs that a lot of recent languages are targeting in various ways and this is I'm going to sort of go through a litany of different kinds of approaches to concurrency in the way that that you can model them in rust and how they help you catch bugs so let me start with one particularly pernicious bug that comes up in concurrency which is the notion of a data race so a data race is defined as having two threads that are accessing a piece of memory where at least one of the threads is doing a bright and those accesses are not synchronized in some way what synchronized means here is is actually quite flexible there are just simple reads and writes where you just say to the compiler this is considered synchronized and that has certain implications about optimizations so I don't necessarily mean locks or something like that it could be much lower level but this goes back to what I mentioned before if you have a date of race in a C or C++ program the compiler can do anything basically your program has no meaning if you have this kind of bug because the optimizer can can sort of break your code arbitrarily but this kind of bug is very very very easy to create by accident okay and so so and it can because of that undefined behavior aspect basically this is problematic for safety as the dangling pointer problems that we were seeing before so if you want a safe language you just can't allow data races at all so the ingredients of data races basically come down to aliasing because you have to have two threads mutation because at least one has to be writing and then no ordering which is another way of saying lack of synchronization and of course those first two bullets are familiar from you know what we've seen so far would the ownership and borrowing so you might you might expect that we could sort of reuse some of the tools that we've seen already and that turns out actually to be the case right so I sort of mentioned that there are a lot of languages trying to tackle concurrency bugs in various ways and many of them propose specific models of concurrency so there's the actor model which is espoused by languages like Erlang for example and there in principle you're you're forbidding aliasing so you're passing around owned values between different actors and on the other hand there are functional languages like Haskell which let you alias all you want but disallowed mutation and rust approach is sort of all of the above in the sense that you can do either of these you can you know as we saw with different kinds of references you could allow or disallow mutation or aliasing and do so at different times okay so that leads to sort of many different concurrency models that you can build up and rust and all of this is done in rust libraries it's not built into the language in any way okay so let me start with a really simple discipline which is basically the actor model yeah because why is it not built-ins playing with so I actually take the serves of your implementation takes concurrency machine and pushes it up for a level or two so that you can do things underneath between rust and the machine to accomplish magic right doesn't being the right thing so it's it's it's pretty interesting um when this started it was taking a very different approach where concurrency was based very deeply into the language um and it sort of followed the actor model but over time what we realized was that these kinds of disciplines of concurrency actually just fall out of the ownership system and I'm gonna show you what that looks like but basically you can build in a library something that gives you the guarantees you want but by not by making it a library and rather than the language it's open-ended anyone can build new libraries you know you can keep adding new concurrency constructs without actually changing the whole language right so I think it's actually a huge benefit I am looking read yes we discussed about nested casting of the life you did some type of bread slide but mister actor speaker otherwise Walter okay good I'm safe cool okay well I mean one of the things I want to say is that rust can do the actor model very nicely and in the following way so so now I'm gonna sort of go back to stick figures for a moment and think about these two figures instead of being different functions that are calling each other different threads that are communicating right and so in the actor model a thread has ownership of some data and that becomes a message that's passed to another thread and that message passing is supposed to designate ownership transfer all right and then that other thread might form some other message and transfer ownership back over and the key thing is that because its ownership there can't be a Lea says so all communication between threads has to go through this message passing okay stick going from stick figures to code here's what this actually looks like in rust and I'll go through this fairly quickly but so we first of all set up a channel and a channel has two ends a transmission end and receive end in rust at least in the particular library we offer and that looks like sort of this diagram in terms of the memory layout so those two ends are sitting there on the stack and then they point to some data on the heap the actual data is not so important then we spawn some child thread okay and this double pipe syntax don't don't worry about it too much it's basically expands out to we have some piece of code we're writing a function just an anonymous function that is going to be the body of the child thread we want to run okay and notice that in that child thread we're mentioning the transmission side okay and there's also this funny keyword move going on right so there's a lot going on here but the point is by writing move and mentioning the transmission side we're saying this child thread is going to take ownership of the transmission side of the channel okay so after after this bond the parent thread is not going to be allowed to use TX anymore just as if it had been passed in a function okay and then when the child throat executes this it allocates a new vector it does some stuff with that vector maybe pushes some data into it and then at some point it sends that vector back across the channel now this send only involves the stock data basically just just like every other ownership transfer we seen which is fairly small right that's just just a few words of data that's being transferred from one thread to the other the bulk of the data which is sitting in the heap didn't move at all right and and that way you're basically you're passing pointers back and forth between threads but you're getting a lot of help from the language to make sure that nothing goes wrong in terms of dangling pointers or aliasing a mutation like I've shown you but this is all just using the existing ownership discipline not even any borrowing it's just pure ownership to transfer these between threads is that fairly clear getting something it seems to me on the one hand you have the physical representation the data structure and the other hand you have the abstract information which may be passed along or copied or moved or mutated and then put it back into a data structure yes and this language differentiate strongly between that or is it basically really talking about the representation of the data member and deals more with the physical layout of that data right okay yeah so basically okay so the question is essentially does there's potentially a distinction between ownership transfer in some abstract sense and the actual physical layout that's involved and how does that look to the language so by default the language unifies the two essentially like right when you write a data type there's a sort of you're you're assuming everything in it is and and so if you if you pass it along you're passing that stack representation but there are various ways to say to the language you that you want some more abstract notion of ownership and when you do that you're Ghent that generally involves breaking out of the ownership system in some way and I'm going to talk about that that little that escape hatch bit at the end but so you have the flexibility to do that and that's very important yes yes there is yes as a library okay here's another different another example of concurrency so we saw the actor model here we're seeing the sort of pure functional model where we're just passing some data to a bunch of threads so that they can read from it right this is a really simple model for concurrency and in this case the thing is we have a bunch of threads that all have access to a vector and it's it may not be clear at what point of they're done with the vector in some sense right there may not be any single thread that clearly owns the vector or is going to outlive the rest and so there's this notion of Arc which stands for atomic reference count which is a smart pointer if you know C++ so it's a kind of pointer that does some interesting stuff when you work with it and basically it's just maintaining a reference count how many threads are actively looking at this object and when the last thread destroys its arc basically then the vector itself is destroyed so in some sense the arc itself owns the vector that's one way of thinking about this but then again you can reason about the layout inside the arc you have the vector just laid out directly exactly as we saw before and because that is stored inside the arc there are no like aliases to the vector itself everything has to go through the arc in particular the arc only allows you to get out a shared reference to the vector as you'd expect because it's the whole point is to allow aliasing between different threads okay so that that's pretty simple there's kind of an interesting aspect to this though so going back for a second to send like you might use with a channel it's the library is set up so that when you were sending a type that type has to be marked sent what does send mean basically you it means thread-safe okay in particular there are two forms of reference counting that are commonly used in practice there's atomic reference counting which uses synchronized accesses to change the reference count and then there's sort of non atomic reference counting which is much faster but it's not safe for usin concurrently so the key thing is that those two types are marked send and not send respectively and therefore you know if you're using the non atomic version there's no chance that it will ever migrate to another thread and create a thread safety problem so Russ is actually checking that when you pass data between threads that data has actually thread safe to use right and that rules out another class of of concurrency bugs okay let's look it's a few other concurrency models so of course we have to talk about locks Russ can do locks and it has a sort of interesting story there as well so with with locks right normally you just have some type mutex that represents a lock and you have the ability to acquire the lock or release it at some point but in in Russ the mutex type actually takes another type as an argument and that other type represents the data that the lock protects and so what's kind of interesting about this is the mutex never gives you direct ownership or access to that data instead it gives you a kind of what's what's known as a guard in C++ so it's a it's a value that points to the data but doesn't give you full ownership and this is a bit subtle but you'll notice that in this function call I have a lock and I say unlock which might seem strange but actually what's going on is that the guard that I get back when I ask for a lock will do the unlock when it's destroyed so I get back this value I can this value data I can use data to to mutate or otherwise access the data protected by the log and in fact I that's the only way I have of getting to that data and the only way I have of releasing the lock is by destroying my guard data okay and so if you put this all together what this means is there's no way to access data that's supposed to be protected by the lock when you don't own the lock right when you're not actually in a critical section ruling out yet another classic class of concurrency bugs okay that that's maybe a little subtle to see but hopefully the gist is clear yes and that's right so this is a point this is a place where the rules are being bent slightly because obviously locking the mutex does involve mutation under the hood but you don't want to require a mutable bar of the mutex because then only one thread could own the mutex right the whole point of a mutex is to allow aliasing across threads so actually what's going on here is very interesting because you know like I just said part of the reason to having me text at all is that you want lots of aliases to some data and for those aliases to be usable for a mutation which sounds like a problem from what I've been telling you but the other aspect of the mutex is mutual exclusion so it's mutex is ensuring at runtime that you never have multiple threads mutating at the same time right and so the overall discipline is still followed it's just being performed sort of dynamically through the synchronization of the mutex rather than completely at compile time okay but that's again just built up as a library does that does that make sense there's a lot going on here so it's a little hard to get but that's a that's a synchronization mechanism around rather simple mind absolutely and because this is all built as a library it's sort of completely open-ended what synchronization mechanisms you build but we have locked free data structures for example and many other forms of more sophisticated like barriers semaphores all kinds of stuff okay now I want to give one one last example switching gears from focusing on concurrency and concurrency bugs to parallelism which is closely related but not exactly the same thing all right so a classic example of an algorithm you might want to parallel eyes is something like quicksort right at dividing conquer style algorithm so here's sequential quicksort written in rust where we're giving we're given access to what's called a slice so I guess I haven't introduced this notation at the top where it says Veck and has these these bars but basically or the the square brackets you can think of that as a sort of fixed size window into a vector so it's not the whole vector it's just a window into it and that's kind of key for what's going on here because in particular right we we start with this whole slice and we're going to pick a pivot point and then once once we've done that we're going to actually partition the slice into two independent slices and there's a function in the library called split at mute and what what that's doing is it's saying you have a reference to some whole slice of a vector a mutable reference and you want to turn it into two mutable references to disjoint subsys all right and so that's safe even though they're both talking about the same underlying vector because they're disjoint you can't actually get into any trouble through by using these two to mutate the underlying data okay and then we go on to actually do this sort so that was the sequential version we can do the same thing in the in the parallel version basically the code looks almost exactly the same so we do the partitioning we get these two sub slices only this time we are actually sort of asking to to do the recursive calls on some child threads right using this parallel join thing now something very interesting is going on here because we have the parent thread the initial call to parallel queue sort and we have two children threads which are actually being given pointers onto the parent thread stack which is a very dangerous thing to do because those pointers must not outlive that stack frame or you're going to be in real trouble this is yet another kind of bug date that is very easy to make in C++ the key point is rust only lets you do this if you're doing something like a join right and the type system can actually talk about whether you you are accessing stack data or not and in order to give out stack data to children threads you have to be joining on those threads and that's important because it prevents that stack frame from being popped well the children threads are running right you're waiting for the children threads to finish before you continue and pop the frame yes sorry which check do you mean about the stuff yes if you if you so what happens is when all data in rust has something called a lifetime associated with it which I haven't talked about at all here but it's sort of implicit and that that lifetime refers to the borrows that are active in that data and there's a way of saying that the lifetime has to be static which means there are no burrows inside and that's a way of ruling out stack data basically or sort of pointers onto stack data so in the libraries what I'm saying is that there's one way to spawn a thread just the spawn keyword that I ordered this on function that I showed you that requires that the child thread have a static lifetime basically that you're saying the data that's being passed cannot have any burrows into the stack but then this join function can take any lifetime so basically the type system allows you to say whether stack data is allowed or not and then the libraries themselves build different api's where they're tying this together with things like joining does that does that help so in this case it doesn't have to be static right that's the point because the join function is allowed to take non-static closures as an argument B precisely because you know that the semantics is it it will join those two threads at the end so the stack trim will be valid for their entire duration but the spawn function that we saw before that has a static constraint and so if you tried to do this with spawn you'd get a compiler error right if I just said spawn twice for the two children that would not compile because of the type signature question is how does join and spawn get the information so they get it by sorry I'm not sure I haven't shown you their type signature so I'm sort of like alluding to this thing that I don't have on a slide unfortunately but basically if you were to put their type signature side-by-side spawn has us actually says static in part of its type for the closure and join does not and then so then you're sort of hooking into the compiler so the compiler knows whether stock data is involved or not and this gives you a way to ask for it that's right so the notion of lifetime's in static that's a language feature this is an example of the libraries taking advantage of it okay I guess I should probably move on just to wrap up on time here okay I sort of watched you there so okay at this point I'm basically going to just conclude the talk so you know I've shown you several different models of you know typical models of concurrency how you can support them in rust and the kind of strong guarantees that you get that rule out classic bugs for each kind of model but as I said because this is all being done with the libraries it's very open-ended and it's under very active development so you know futures or promises are one thing we have some you know small support for it but we want to do a lot more and I mean this is just an endless list but one key point is that all of these sort of approaches to concurrency and when you build them and rust our data race free by by construction okay and now I sort of pull back the curtain all right so I've been sort of alluding to this all throughout the talk that the discipline can be too strict you need to bend the rules in certain ways right which you know should make you uncomfortable it certainly makes me uncomfortable so one of the key things that makes Russ work sort of workable is the notion of unsafe code so there's a key word in rust called unsafe and basically within an unsafe block you are allowed to break certain rules that are normally in play which may sound like you're just throwing all of these great guarantees I mentioned out the window right I mean that's that's the typical first response like if you have this what's the point and you know from a from a sort of purely theoretical point of view that's totally true as soon as you have one unsafe there are no guarantees but the key is in practice you actually need unsafe relatively rarely usually in data structure libraries or like concurrency libraries and you can use it in a in a constrained way and in particular you can hide uses of it behind an abstraction boundaries so that you can say if you build a mutex for example under the hood that uses unsafe that the rules in certain ways but it guarantees at the public API level the things you can actually do with the mutex are perfectly safe and in fact every use of unsafe has to make that guarantee it has to be hidden behind the boundary so that users of that library get all the guarantees of Rusted back so it's encapsulation of safety so that is proved on the side right so this is not something this is not something that the compiler at least today can give you help with right but you can search for uses of unsafe in your code right you sort of you can find the places where you might be breaking the rules and there's a strong sort of culture and rust and it's ecosystem to avoid unsafe whenever possible right so it's much easier to audit your code and actually there is ongoing work in academia to try to go push this further and actually understand what it would take to prove unsafe abstractions safe right and maybe build some additional tools to help you do that in the at the end of the day you know if this is going to work it has to be practical and you can't you can't have a completely rigid set of rules like the ones I showed you and scale all the way up to a grab web browser but our experience so far says you can get a really long way using unsafe quite minimally okay and then just just sort of information about where rust is right now so we are deeply in the midst of a sort of final push toward a 1.0 released so at the end of this month we'll put out a beta and then six weeks later we'll put out the actual 1.0 final and one of the things up to this point we've been breaking code on a daily basis so it's been very hard for people to use rust in a production way but that's going to change with 1.0 so this is the point where it switches from research project to something real that people can build on so obviously I'm very excited great well I think I'll just leave these conclusions up here and take any final questions we do you should download it and try it out so it's egg falls very rarely yeah so we have servo servo runs you can browse the rust github repository on servo you can actually do quite a bit it runs on phones and in fact this year one of our goals is to have a component of module of code that's shared between the servo browser and also Firefox so we're sort of trying all avenues for getting rust actually in use in the organization I think once we're shipping one module then they're stuck with it and Mozilla has to deal with rest forever that's our that's our secret hope challenge oh man what doesn't so that's actually that's a really interesting question um I I do I worried that yeah well you said besides the unsafe but I think I mean on whether whether this vision about unsafe being constrained enough will actually succeed is one thing that keeps me up at night and the fact that right now to program even a simple data structure like a doubly linked list that involves aliasing a mutation requires a little bit of unsafe code that worries me but I think we have a lot of ideas for how to push the boundary further to give you building blocks that are safe that you can use to build things like linked lists but I don't know I mean we're sort of doing the best we can to you know make all these promises like I said we're not gonna break any code after 1.0 and so the other thing that keeps me awake at night is like what if we made some of those decision wrong and you know we haven't had enough time for a feedbag after the zero point yes yes that's an interesting question um I don't there aren't a lot of languages that are targeting this combination of safety and control in in a way I think we're the only game in town they're certainly most most other languages that are trying to give you that level of control at least require a garbage collector but I think Russ is often compared to go in part because go is also by you know a company that makes a web browser and it's a recent language you know people are sort of looking for a new thing I think those are I think go and rust are targeting pretty different niches honestly so I I mean C++ actually if I was to be honest I think C++ is the biggest competitor to rust and part of the reason I say that is rust well C C++ has come a really long way and a lot of the ownership stuff I'm showing you in rust resembles what it's like to write C++ code these days it's just that C++ doesn't actually check it for you and a lot a lot of it is done at runtime right and there's a lot of cruft in C++ it's an old language right but I think that's the biggest risk is that C++ might get good enough that rust just never has a chance to that's that's an interesting question I think that at you know there are sort of two answers to that one is a theoretical answer and one is a practical answer so in theory the core discipline of ownership and borrowing I think is very widely applicable to many different architectures and styles of programming so I think you know even if you know cache coherence goes out the window for example I think that ownership will still be an important aspect of programming and something that the type system can offer you on the other hand practically we're very tied to LLVM right now and who knows how long that will last you know hopefully someday there will be a Russ standard and many rest implementations but that's a long way off I don't think you said so but I'm assuming that in rust you can't manipulate pointers in the sense of incremental it's not the pointers I showed you there's there is a kind of unsafe pointer that you can only manipulate in unsafe code but that's that's used very rarely so if you thought about writing arrests on this people have wait I mean huh hobbies hobbyists have nothing too serious but that's definitely something you could imagine doing there's a lot of work needed to I mean right now like Russ works relatively smoothly because you can assume an allocator and the libraries are sort of shaped around that and for an OS things are a little bit different so I think the standard library would have to change quite a bit to write a serious OS but there are lots of hobby projects doing it already so it's very easy to link it to C libraries and that that was that actually drove a lot of the recent design so that in comparison to go for example it's much easier Russ just looks like C C++ it's this story's not as good except that C and C++ are relatively easy to interface together so you at least have that starting point but Russ doesn't have anything like classes right now it has other features that give you parts of what classes do so that that story will evolve over time I think it's essential social comment such a explanation is and limiting damage I have to go back back to my grandfather used to borrow tools but yeah this very strict rule whenever you trim your tool you bring it back in the same state over there no it's right courtesy born in sweats when I came the US this year why any professor in mechanical engineering this thing about designing bikes I never said please the first full she had well you're getting these old bikes and you have to return about they're on the court in a better state bingo unfortunately she didn't do that that's not what they put it's bits yeah it's a social thing this I accept it today well she sort of thought it was cute and she abandoned it and yeah but but take your painful fire systems on safety human thorald nicely right right then you would have a problem right so anyway yeah it's I just shopped in some sense that's why I ask probing questions to you because I knew where I wanted to go to give me the answers yes keeping it so well I'm glad you discovered thank you thank you you odds because languages like C and C++ that give you the kind of control you need to get the performance for a competitive browser also leave you open to all kinds of security vulnerabilities right and so you're constantly hearing this you know Firefox has been patched or Chrome has been patched you know somebody can break into your machine through a website so the idea is we want to build the next generation web browser which is a project called servo and we want to do it in a new language that tries to do better at giving you safety and control at the same time okay and so rest and server are kind of this symbiotic pair of research projects okay so that's that's the really high level picture and I want to spend a little bit of time going into more detail about exactly what I mean by control and what I mean by safety so let's start with control so C++ is is sort of the best existing language in this respect and even in just a small snippet of code we can start to see the kind of control that you get over the machine so for example if we declare a vector in C++ we know exactly how that's going to be laid out in terms of you know the the memory so in particular there are some fields of the vector including a pointer to the actual data and some metadata about it that all live on the stack and it's very important that that data is allocated on the stack and then there's a pointer that goes directly to the heap on and in this particular interest you have traditional systems programming languages like C and C++ which I'm sure all of you are familiar with and then languages that might be a little less familiar like ml and Haskell actually I'm curious are who in the room is familiar with either of those languages okay so quite a few people that's great so rust is sort of trying to blend the best of both of these languages to get up this problem of safe systems programming what's that okay so so one question you might wonder is why is Mozilla interested in creating a programming language so you probably all of you know that Mozilla is the organization that created the Firefox web browser which is currently written in C++ and it turns out actually that web browser have a sort of need for a combination of things that are in deep tension so on the one hand web browsers need a high degree of control over the machine they're they're sort of asked to do very complex tasks very quickly they're us to display media they're asked to have you know all of these tabs running simultaneously running code that's untrusted you know that's been downloaded from the internet and then on the other hand they need safety because they're running this untrusted code from the internet right and everybody is out there trying to break into your machine and these two are sort of regular case where we have a vector that's containing a string and we sort of have the same pattern again where the string is itself another vector and the relevant sort of pointer and metadata is stored in line in the array so to actually get at a piece of string data you follow these two in directions one to get into the the vector itself on the on the heap and then one more to get into the string and we'll contrast that with some other languages in a second but the the high-level point here is you have you know exactly what things are going to look like in memory and you you have a lot of control over that layout in addition C++ gives you various kinds of pointers including things like these lightweight references which can point into the heap and they can point into the stack and there's there's no overhead for doing so you have sort of all of the power you're normally used to with pointers and then finally there's something implicit here which if you know C++ is sort of obvious so at the end of this function the vector goes out of scope and as a result it's destructor is run and it's memory is freed you didn't have to say anything for that to happen so that's a nice convenience but on the other hand you know exactly when it's going to happen by reasoning about the scope so that's in contrast to something like garbage collection where the data might be freed at some point in the future but you don't know exactly when you can't control it it might be inconvenient okay one of the principles that you get out of C++ is something often called zero cost abstraction and what this means is that you can build up libraries like vectors and strings that are reasonably convenient to use they give you a nice abstraction but when you compile it down it's nothing different than you know something you could have written by hand in assembly you're sort of not giving up any performance in doing this okay now if you look at a language that is safe but doesn't give you control like Java this story is very different so in Java if we tried to sort of set up the same scenario where we have a vector containing strings we have layers in layers of indirection because Java enforces a kind of uniform representation on everything it doesn't give you control over stack layout versus he play out and so on and so forth right and so in Java any abstraction comes at a cost and I want to I want to be clear here I've been focusing on memory layout as a relatively simple example but this applies to many many things so being able to choose between static dispatch and dynamic dispatch again in Java basically everything is dynamically dispatch everything is going through objects in C++ you have the choice template expansion is related to that and there are there are many more examples so what we what we want is this to retain this notion of zero cost abstractions and control from C++ but to somehow get the kind of safety that languages like Java and the you thank you so much for the intro yeah so I'm here in Tehran I'm an employee at Mozilla research and I'm really excited to tell you about our rust project which is you know been an ongoing research project for the last sort of four or five years in terms of having a lot of manpower behind it and is just on the cusp of actually being introduced in 1.0 sort of to the world to use as a more serious product so so my background is in programming languages I did research in programming languages and it's it's kind of an interesting time to be a person working in programming languages because for for a long time it had been considered a sort of dead area everybody was happy with C or Java or what-have-you but in the last few years there's been this kind of resurgence of interest in new programming languages with lots of different industry groups coming out with languages including Facebook and of course Mozilla so with all these languages coming out you might ask okay what you know what is sort of special about rust why rust why is Mozilla building rust so the the kind of headline for rust is safe systems programming I'm gonna explain what I mean by that in the next few slides but in terms of sort of the the lineage
Info
Channel: Harvey Oberbrunner
Views: 37,469
Rating: 4.9045997 out of 5
Keywords: Aaron Turon, mozilla, firefox browser, rust programming language, Stanford, Stanford University, SCPD, ee380
Id: SZvs15hC81U
Channel Id: undefined
Length: 72min 24sec (4344 seconds)
Published: Mon Dec 04 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.