Placeholder Image

Subtitles section Play video

  • [? RAIF LEVINE: All ?] right.

  • Thanks everybody for coming today.

  • I'm [? Raif ?] Levine of the Android UI Toolkit Team.

  • And it is my pleasure to introduce today Alex Crichton

  • of the Mozilla Research.

  • And he is a member of the Rust core team.

  • And he is here to tell us about Rust,

  • one of the more exciting and interesting languages,

  • I think, in the past few years.

  • ALEX CRICHTON: Thank you, [? Raif ?].

  • So I'm going to give just a whirlwind tour of what Rust

  • is, why you might feel like using it,

  • and what are the unique aspects of it.

  • So the first thing that I normally

  • get if I ever talk about a programming language is

  • why do we have yet another one?

  • So if you take a look around, we have this whole landscape

  • of programming languages today, they all fill various niches.

  • They solve lots of problems.

  • But it turns out that you can organize these languages

  • along a spectrum.

  • And the spectrum is this trade-off

  • between control and safety.

  • So on one end of the spectrum, we have C and C++,

  • which give us lots of control.

  • We know exactly what's going to run our machine.

  • We have lots of control over memory layout.

  • We don't have a lot of safety.

  • We have segfaults, buffer overruns, very common bugs

  • like that.

  • Whereas on the other side, we have JavaScript.

  • We have Ruby.

  • We have Python.

  • They're very safe languages, but you don't quite

  • know what's going to happen at runtime.

  • So in JavaScript, we have JITs behind the scenes,

  • or you really don't know what's going to happen.

  • Because it'll change as the program is running.

  • So what Rust is doing is completely

  • stepping off this line.

  • Rust is saying, we are not going to give you

  • a trade-off between control and safety.

  • But rather, we're going to give you both.

  • So Rust is a systems programming language which is kind

  • of filling this niche that hasn't been filled by many

  • of the languages today, where you get both the very low-level

  • control of C and C++, along with the high-level safety

  • and constructs that you would expect from Ruby,

  • and JavaScript, and Python.

  • So that might raise a question.

  • We have all these languages.

  • What kind of niches will benefit from this safety

  • and this control?

  • So, suppose you're building a web

  • browser, for example, Servo.

  • Servo is a project in Mozilla Research

  • to write a parallel layout engine in Rust.

  • So it's entirely written in Rust today.

  • And it benefits from this control, this very high level

  • of control.

  • Because browsers are very competitive in performance,

  • as we all very much well know.

  • But at the same time, all major browsers today are written

  • in C++, so they're not getting this great level of safety.

  • They have a lot of buffer overruns.

  • They have a lot of segfaults, memory vulnerabilities.

  • But by writing in Rust, we're able to totally eliminate

  • all these at compile time.

  • And the other great thing about Servo

  • is this parallelism aspect.

  • if you try and retrofit parallelism onto, for example,

  • Gecko, which is millions of lines of C++,

  • it's just not going to end well.

  • So by using a language which from the ground up

  • will not allow this memory unsafety,

  • we're able to do very ambitious things like paralyzing layout.

  • And on the other spectrum of things,

  • let's say you're not a C++ hacker.

  • You're not a browser hacker.

  • You're writing a Ruby gem.

  • Skylight is a great example of this.

  • It's a product of Tilde, where what they did

  • is they have a component that runs inside of the customer's

  • Rails apps will just kind if monitor how long it takes

  • to talk to the database, how long it takes for the HTTP

  • request, general analytics and monitoring about that.

  • But the key aspect here is that they have very tight resource

  • constraints.

  • They're a component running in their client's application.

  • So they can't use too much memory,

  • or they can't take too long to run.

  • So they were running into problems,

  • and they decided to rewrite their gem in Rust.

  • And Rust is great for this use case because with the low level

  • of control that you get in C+, C, and C++,

  • they were able to satisfy these very tight memory constraints,

  • the very tight runtime constraints.

  • But also, they were able to not compromise the safety

  • that they get from Ruby.

  • So this is an example where they would have written their gem

  • in C and C++.

  • But they were very hesitant to do so.

  • They're a Ruby shop.

  • They haven't done a lot of systems programming before.

  • So it's kind of tough, kind of that first breach into systems

  • programming.

  • And this is where Rust really helps out.

  • So I want to talk a little bit about what I mean by control

  • and what I mean by safety.

  • So this is a small example in C++.

  • Well, the first thing we're going to do

  • is make a vector of strings on the stack.

  • And then we're going to walk through and take

  • a pointer into that.

  • So the first thing that we'll realize

  • is all of this is laid out inline, on the stack,

  • and on the heap.

  • So, for example, this vector is comprised

  • of three separate fields, the data, length,

  • and capacity, which are stored directly inline on the stack.

  • There's no extra indirection here.

  • And then on the heap itself, we have

  • some strings which are themselves

  • just wrapping a vector.

  • So if we take a look at that, we'll

  • see that it itself also has inline data.

  • So this first element in the array on the heap

  • is just another data, length, and capacity,

  • which is pointing to more data for the string.

  • So the key here is that there's not these extra layers

  • of indirection.

  • It's only explicitly when we go onto the heap,

  • we're actually buying into this.

  • And then the second part about control in C++ is you have

  • these very lightweight references.

  • So this reference into the vector,

  • the first element of the vector is just this raw pointer

  • straight into memory.

  • There's no extra metadata tracking it.

  • There's no extra fanciness going on here.

  • It's just a value pointing straight into memory.

  • A little dangerous, as we'll see in a second,

  • but it's this high level of control.

  • We know exactly what's going on.

  • And then the final aspect of this

  • is we have deterministic destruction.

  • Or what this means is that this vector of strings,

  • we know precisely when it's going to be deallocated.

  • When this function returns is the exact moment at which

  • this destructor will run.

  • And it will destroy all the components

  • of the vector itself.

  • So this is where we have very fine-grained control

  • over the lifetime of the resources

  • that we have control of, either on the stack

  • or within all the containers themselves.

  • And what this mostly boils down to

  • is something that we call zero-cost abstractions, where

  • this basically means that it's something that at compile time,

  • you can have this very nice interface, very easy to use.

  • It's very fluent to use.

  • But it all optimizes away to nothing.

  • So once you push it through the compiler,

  • it's basically a shim.

  • And it'll go to exactly what you would

  • have written if you did the very low-level operations yourself.

  • And on the other this side of this,

  • let's take a look at Java.

  • So if we take our previous example of a vector of strings,

  • then what's actually happening here

  • is the vector on the stack is a pointer to some data,

  • the length, and some capacity, which itself

  • is a pointer to some more data.

  • But in there, we have yet another pointer

  • to the actual string value which has data, length, and capacity.

  • We keep going with these extra layers of indirection.

  • And this is something that's imposed on us

  • by the Java language itself.

  • There's no way that we can get around this, these extra layers

  • of indirection.

  • It's something that we just don't have control over,

  • where you have to buy into right up front.

  • Unlike in C++, where we can eliminate these extra layers

  • and flatten it all down.

  • And when I'm talking about zero-cost abstractions,

  • it's not just memory layout.

  • It's also static dispatch.

  • It's the ability to know that a function call at runtime

  • is either going to be statically resolved or dynamically

  • resolved at runtime itself.

  • This is a very powerful trade-off

  • where you want to make sure you know what's going on.

  • And the same idea happens with template expansion,

  • which is generics in Java and C++,

  • where what it boils down to is that if I have a vector

  • of integers and a vector of strings,

  • those should probably be optimized very,

  • very differently.

  • And it means that every time you instantiate those type

  • parameters, you get very specialized copies of code.

  • So it's as if you wrote the most specialized

  • vector of integers for the vector of integers itself.

  • And so, let's take a look at the safety aspect.

  • That's an example of what I mean by control.

  • But the safety comes into play especially in C++.

  • So this is a classical example of where

  • something is going to go wrong.

  • So the first thing that we do is we have our previous example

  • with the vector strings.

  • And we take a pointer into the first element.

  • But then we come along, and we try and mutate the vector.

  • And some of you familiar with vectors in C++,

  • you'll know that when you've exceeded the capacitive

  • of vector, you probably have to reallocate it, copy some data,

  • and then push the big data onto it.

  • So let's say, in this case, we have to do that.

  • We copy our data elsewhere, copy our first element

  • that's in our vector, push on some new data.

  • And then the key aspect is we deallocate the contents

  • of the previous data pointer.

  • And what this means is that this pointer,

  • our element pointer is now a dangling pointer

  • in the freed memory.

  • And that basically implies that when we come around here

  • to print it out onto the standard output,

  • we're going to either get garbage, a segfault,

  • or this is what C++ calls undefined behavior.

  • This is the crux of what we're trying to avoid.

  • This is where the unsafety stems from.

  • So it's good to examine examples like this.

  • But what we're really interested in

  • is these fundamental ingredients for what's going wrong here.

  • Because this kind of example, you

  • can probably pattern match and figure it out.

  • It's very difficult to find this in a much larger program

  • among many, many function calls deep.

  • So the first thing we'll notice is there's

  • some aliasing going on here.

  • This data pointer of the vector and the element

  • pointer on the stack are pointing to the same memory

  • location.

  • Aliasing is basically where you have two pointers pointing

  • at a very similar location, but they don't know anything

  • about one another.

  • So then we come in and mix in mutation.

  • What we're doing is we're mutating

  • the data pointer of the vector.

  • But we're not mutating the alias reference

  • of the element itself.

  • So it's these two fundamental ingredients in combination--

  • aliasing and mutation.

  • It's OK to have either of them independently.

  • But when we have them simultaneously

  • is when these memory and safety bugs start to come up.

  • So I'll harp on this a lot more in the rest of this talk.

  • And we'll see a little bit more in detail.

  • So you might be asking, what about garbage collection?

  • This sounds like a problem that garbage collectors

  • are tasked to solve, forbidding dangling pointers,

  • forbidding these various references,

  • rewriting things at runtime.

  • But it turns out that garbage collectors

  • don't come without a downside.

  • Garbage collectors don't have a lot of control.

  • They don't have this low-level of C and C++.

  • You have these GC pauses.

  • You have these variable runtimes actually allocating data.

  • It's very tough to reason about what's going to happen.

  • And then an aspect of garbage collectors

  • which is not really cited that often is they require runtime.

  • And this is a very strong aspect of C and C++ where,

  • for example, if I write a library in C,

  • I can run it basically anywhere.

  • Because there's no assumption about the host

  • runtime that I'm running inside of.

  • So with a garbage collector, it's

  • very difficult to embed what you're

  • writing into other languages.

  • And we'll see how it's really beneficial to not have

  • this runtime behind the scenes.

  • And then the worst part about this

  • is that it's insufficient to solve all the problems that we

  • want to solve.

  • Garbage collectors don't automatically

  • get you away from iterator invalidation, from data races.

  • These are these concurrency bugs that

  • are very difficult to weed out.

  • So it turns out that all of these problems

  • are very closely interrelated.

  • And we would love to solve them all at once.

  • And garbage collection doesn't quite fit the bill.

  • So what Rust has is a system called ownership/borrowing.

  • This is going to be the main focus for the rest of the talk.

  • I'm going to talk a lot about ownership/borrowing

  • and some of the great things we'll get out of it.

  • But it suffices to say for now that ownership/borrowing

  • doesn't need a runtime.

  • It's totally aesthetic analysis [INAUDIBLE].

  • It's zero-cost at runtime.

  • It's all static analysis.

  • There's nothing going on under the hood

  • when you're actually running code.

  • It's totally memory safe.

  • This is the crux on which all of the memory safety of Rust

  • is built is these concepts of ownership/borrowing.

  • And finally, using these, we're able to prevent all data races.

  • Which means that all your concurrent code

  • you write in Rust, you will not have a data race.

  • Which is an incredibly powerful thing to say if we're coming

  • from, for example, C or C++, where it's very difficult

  • to know if you ever might have a data race or it won't.

  • And we take a look back again at C++.

  • C++ gets us one of these things.

  • We don't need a runtime with C++.

  • Garbage collection also gets us one of these things.

  • It gets us memory safety.

  • But the great thing about ownership/borrowing

  • is this free abstraction at runtime

  • gets us all of these simultaneously.

  • So I want to give some credit to the Rust 1.0 community,

  • in the sense that we would not be here at 1.0 without them.

  • The Rust community is very large, very active,

  • incredibly helpful, incredibly kind, incredibly welcoming.

  • And you can see this across our forums,

  • IRC our Subreddit, on GitHub, on issues and pull requests,

  • basically everyone is proud to be a part of this.

  • And they were absolutely instrumental to coming

  • across the finish line for 1.0, both in terms

  • of giving feedback on our designs,

  • proposing their own designs.

  • I would highly encourage anyone who's interested in Rust

  • to jump in.

  • And don't feel afraid to ask stupid questions

  • or ask any questions anywhere.

  • Because it'll be answered almost instantly

  • by someone who's super helpful.

  • All right, so I want to take the rest of this talk

  • and talk a lot about what I mean by ownership,

  • and what I mean by borrowing, and some of the great things

  • that we're going to get out of this.

  • And the first of this is ownership.

  • So ownership is basically the English term,

  • what it means by it.

  • I own a resource.

  • So in this case, let's say I own a book.

  • I can then decide to send this book to someone else.

  • And I can then decide to go away.

  • And we knew that this book, because it has an owner,

  • we don't deallocate it or anything.

  • It's totally independent of me, the previous owner,

  • at this point.

  • But as the owner of a book, I can then also

  • decide to go away.

  • At which point, we know this book has not

  • moved to anyone else, so we can totally destroy it.

  • We can release all resources associated with this book.

  • So I was talking about these fundamental ingredients

  • of aliasing a mutation.

  • And we'll see that with ownership, we're

  • totally forbidding the aliasing aspect.

  • We're still going to allow free mutation through these.

  • But by forbidding aliasing, we're

  • still going to prevent us from this whole class of memory

  • unsafety.

  • So I'm going to walk you through an example of what's actually

  • happening here at runtime, what's actually going on

  • under the hood.

  • So here's a small example where we're

  • going to create a vector on the stack.

  • And you notice that like C++, there's no extra layers

  • of indirection here.

  • These fields are stored directly on the stack, this data,

  • length, and capacity.

  • We're going to come along and push some data onto it,

  • so push a 1, push a 2.

  • And then we'll get to the point that we're going

  • to call this function take.

  • And the key part about this function

  • is that it says that it's going to take ownership

  • of the specter of integers.

  • That's this Vec.

  • It's taking that bare type.

  • What it means is, it's taking ownership.

  • It's consuming ownership.

  • So on the give side of things, what's actually going to happen

  • is we're going to create a shallow copy at runtime

  • of this vector.

  • Now we'll notice that there's some aliasing going

  • on here, which is what ownership is preventing.

  • So the key aspect is that we're going

  • to forget this previous copy.

  • This copy that give has-- the shallow copy--

  • is totally forgotten.

  • And we no longer have access to it.

  • So then as the function take runs,

  • it now has ownership of the vector.

  • It has its own shallow copy.

  • And it can do whatever it wants.

  • It can read from it.

  • It can write from it.

  • It can do basically [INAUDIBLE] however it likes.

  • And then once we get to the end of the function take,

  • we know that we have not passed ownership of the specter

  • anywhere else.

  • So this is the precise time at which we could deallocate it.

  • We know that there's no one else who can have access to it.

  • We're the sole owners of it.

  • So as a result of it falling out of our scope,

  • we can free all the resources associated with the vector.

  • And then once we come back to the function give,

  • the vector has been forgotten, so we

  • don't have to worry about using a dangling reference.

  • And so the compiler is the one that's enforcing these moves.

  • When we call the function take, we move the vector Vec

  • into the function take.

  • And if, for example, we tried to actually use the vector

  • after we had moved it, say, for example,

  • we try to push some [INAUDIBLE] onto it,

  • the compiler rejects this, saying

  • the vector has been moved.

  • And so we are not allowed to have access to it.

  • And this primarily prevents use after free.

  • So this is this entire class of bugs.

  • Because we're tracking the owner of a value, who can access it

  • at any one point in time.

  • Then we know that when the owner has gone out

  • of scope, that the resource has now been freed.

  • And there are no variable that could possibly

  • be referencing it.

  • Because the sole owner has gone out of scope.

  • So this all sounds great.

  • But it's kind of clunky if we only had ownership.

  • So we need the system which we call borrowing,

  • which kind of helps us lend out a value

  • for a small period of time.

  • So I have ownership.

  • I can pass it along.

  • Someone can pass me back ownership,

  • but it's not very ergonomic to do that.

  • So borrowing is what comes into play

  • where I, as the owner of a value,

  • can then decide to lend it out for a period of time.

  • But I'm still in control of the lifetime of the resource.

  • You don't get to control it.

  • You can access it, but I'm the one

  • that's going to decide where to free it and when to free it.

  • So we have two primary kinds of borrows

  • in Rust, the first of which is a shared borrow.

  • And a shared borrow means what it

  • says, where I, as the owner of a resource,

  • can send out multiple copies of my resource, just

  • references to it, into [INAUDIBLE] functions

  • or threads, and yes.

  • And then what's actually happening here

  • is the aliasing and mutation that we're

  • trying to prevent simultaneously,

  • we're totally forbidding the mutation aspect of this.

  • Because there's aliasing happening via shared borrows,

  • we're not going to allow mutation through the shared

  • borrows themselves.

  • And as you might guess, on the flip side,

  • we have mutable borrows, where mutable borrows,

  • I, as the owner, can lend out a mutable borrow to someone else.

  • They can then pass it along to someone else that they like.

  • And then it will implicitly return back on up the stack

  • once everyone starts returning.

  • So what's going on here is these two ingredients

  • of aliasing and mutation, the mutable borrows,

  • unlike the shared borrows, are preventing

  • aliasing but allowing mutation.

  • So these are two different kinds of references

  • to get these two aspects of either aliasing or mutation.

  • But they're in totally separate classes.

  • Because if they were to overlap, then

  • we have them simultaneously.

  • And that's where memory's unsafety bugs come out.

  • So let's take a look back at our previous example of a shared

  • reference going on here.

  • So we have the same idea.

  • We have a vector pushing the data onto it.

  • But then instead of passing ownership to this function use,

  • we're going to pass a borrow for it.

  • So that's what this ampersand sigil out in front means.

  • This ampersand means that I'm taking a borrow of this vector.

  • I'm not taking ownership.

  • And that's the same idea on the caller side.

  • I have this ampersand out in front to say,

  • I'm loaning out ownership of the resource that I currently own.

  • And then you're going to be able to access it

  • for a small period of time.

  • So at runtime, what's actually happening is

  • we're just creating a raw pointer.

  • That's all references are.

  • And this vec pointer is just pointing directly

  • onto the stack itself.

  • And then the function use is going

  • to use this raw pointer, read whatever it likes from it.

  • And then once it's returned, this reference

  • has gone out of scope.

  • So we totally forget about the reference.

  • So another thing to emphasize is this.

  • If this function use tried to, for example,

  • mutate the vector by pushing some more data onto it

  • or for modifying some various elements of the array,

  • those are all completely disallowed.

  • Because mutation through a shared reference

  • is not allowed in this case for vectors of integers.

  • So these two are forbidden by the compiler saying that you

  • cannot mutate through a shared reference.

  • And this isn't 100% true.

  • There are some controlled circumstances

  • in which we do allow mutation through shared references.

  • I'll talk about a few more of them later on in the talk.

  • But it suffices to say that if you see this &T.

  • what it means is that you cannot mutate it.

  • It's essentially immutable.

  • So let's take a look at some mutable references now.

  • So the mutable references are denoted by this &mut tag.

  • And because we have a mutable reference,

  • the compiler is going to allow us to do things like push.

  • So in this example, we're just going

  • to iterate over one vector, push some data onto a new vector,

  • and just create a separate copy of that.

  • So I want to walk you through what's

  • happening here at runtime or how this iteration actually

  • happens.

  • And the first thing that we're going to do

  • is we're going to create this element point.

  • Our iterator is going to give us a pointer into the vector.

  • And those pointers are pointing directly into the first slot.

  • This is kind of like the element pointer we saw earlier in C++.

  • And then when we come around to actually push some data

  • onto the new vector, we're just going to read that pointer,

  • push some data, and then run to the next leaf of the iteration.

  • But the key idea here is that iteration is the zero-cost

  • abstraction, as fast as you would write it in C and C++.

  • This increment stuff, all it's doing

  • is swinging this pointer down to the second slot and then just

  • kind of forgetting about the first pointer.

  • So some of you might have seen an example like this before.

  • Now, you might be wondering what if this from vector

  • and this to vector are equal.

  • What if we're trying to push onto what we're

  • reading from at the same time.

  • So if we walk through and see what's going on, let's say we

  • have that reallocation happen, like we saw at the beginning

  • with C++.

  • So we have our vector of 123.

  • We push the first element, reallocate.

  • We now have 1231.

  • But the key thing that's going to go wrong

  • here is, as we go to the next loop of the iteration,

  • we're going to update this element pointer.

  • But now it's pointing into freed memory.

  • This dangling pointer just was hanging out here.

  • And this would be a problem if Rust were going to allow this.

  • But if we actually try to write this down in Rust,

  • we can actually see what's going on here.

  • The first thing we'll do is we'll

  • take out a shared reference.

  • And the next thing that we'll do is

  • try to take out this mutable reference.

  • But what's actually going to happen

  • is the compiler is forbidding both shared references

  • and mutable references from happening at the same time.

  • So the compiler has this notion of when

  • a shared reference is active and when

  • a mutable reference is active.

  • And these can never overlap, because that

  • would be allowing simultaneous mutation and aliasing.

  • And this ends up giving us a very nice property

  • of mutable references in that a mutable reference is

  • the only way to access the data that it contains,

  • which is a very strong guarantee to provide

  • that if I have a mutable reference,

  • I am the only one that can either read or write

  • to this data at this point in time.

  • And this also applies across many threads as well.

  • So we'll see later how we can leverage

  • these kinds of guarantees and leverage

  • these kinds of static guarantees that Rust gives us

  • in various fashions.

  • So this might seem a little unwieldy

  • if you're looking at, OK, I can either

  • have a shared reference or a mutable reference.

  • But I've got to make sure they somehow match up.

  • But I don't want to have to contort code to make sure

  • that it actually works out.

  • So in this case, we're going to take a pointer into the vector,

  • just &vec[i].

  • But the compiler, when we come down to this vec.push,

  • this needs to be disallowed.

  • Because this pointer could become

  • invalid if we allow the push.

  • So the compiler has this notion of the lifetime of references.

  • So know that the lifetime of this elem reference

  • is the scope of this for loop.

  • And we know that because the mutation happens

  • in the lifetime of the shared reference,

  • it's completely disallowed by the compiler.

  • And it prevents these two from happening at the same time.

  • But once we've gone outside the loop,

  • the compiler knows that the shared reference has fallen out

  • of scope.

  • There's no active shared references,

  • so we can allow mutation.

  • And what this basically boils down to

  • is that in code, basically, you can chunk up

  • the time which a vector is alive for

  • or any resource is alive for.

  • And it can either be borrowed in a shared fashion

  • or borrowed in a mutable fashion.

  • And it can happen a bunch of times.

  • They just can never overlap.

  • So those are the fundamental concepts

  • of ownership/borrowing in Rust, this whole idea of I

  • own a resource.

  • I can pass it around to other threads.

  • But I can pass around ownership so that you

  • get to control the lifetime.

  • But I can also lend on a borrow, where you can either read it,

  • or you can mutate it.

  • But I'm still in control of the lifetime

  • of the resource itself.

  • So I want to give you a taste of how using these two concepts

  • baked into the language, which are somewhat simple,

  • we can build up concurrency abstractions.

  • We can build up these great concurrency libraries.

  • And one of the great things about Rust

  • is that there's all these ways to tackle concurrency.

  • There's message passing.

  • There's shared memory.

  • There's mutexes.

  • There's all these different paradigms.

  • But in Rust, these are all 100% built into libraries.

  • None of these are actually found in the language itself.

  • Because they're all leveraging ownership/borrowing

  • to give you a safe interface at compile time.

  • And one of the other cool things we'll see about this

  • is that you typically have these standard best practices

  • whenever you're using these paradigms.

  • And Rust is going to statically enforce

  • that you must follow these best practices.

  • You cannot break them.

  • And I'll show you some examples as we go through.

  • So the fundamental thing that we're trying to prevent

  • is something called a data race.

  • A data race is what happens when two threads,

  • in an unsynchronized fashion, access the same memory

  • so where at least one is a write.

  • And in terms of C++, this is called undefined behavior.

  • A data race will lead to undefined behavior.

  • And basically, what's happening here

  • is because we use LLVM as a back end,

  • LLVM assumes that a data race is undefined behavior.

  • So the optimizer can do whatever it

  • wants if it detects the code could have a data race.

  • So we need to prevent this to prevent our optimization

  • passes from going awry and doing all sorts of crazy stuff.

  • If you take a look at the ingredients for a data race,

  • like if we were looking at some ingredients for the memory

  • unsafety we saw earlier, these three ingredients

  • of aliasing with more than one thread,

  • with mutation, where at least one's a write,

  • and then unsynchronized.

  • It turns out two of these sound pretty familiar.

  • Two of these sound like our previous system

  • of ownership/borrowing are going to help us also forbid

  • data races at the same time.

  • So I want to talk first about messaging, message passing,

  • where this is going to leverage ownership

  • in Rust, where I have ownership of a message.

  • Another thread comes along.

  • I can then send them a message.

  • And then they can also decide to send me a message.

  • And the key idea here is that we're passing ownership

  • of messages between threads.

  • So typically, whenever you're using message passing,

  • you have this best practice that once you've sent a message,

  • you no longer access what you just sent.

  • But you don't really have a static guarantee

  • that you're not accessing what you just

  • sent across the other thread.

  • So what Rust is going to do is, because of ownership, we

  • can ensure that once we've put a message into a channel

  • or across the threads, I no longer have access to it.

  • So we're enforcing this isolation

  • of threads at compile time all with no overhead and zero-cost.

  • So to run you through an example,

  • we have two threads here.

  • The parent thread takes the receiver end of a channel.

  • And it's going to spawn a child thread, which

  • is going to take the transmission onto this channel.

  • And what we're going to end up with

  • is these two pointers pointing at shared memories.

  • The shared state of the channel itself

  • is the queue of messages on the channel.

  • And then as the child starts running,

  • it's going to create a vector on its stack.

  • It's going to push some data onto it,

  • add some new data onto the vector.

  • And then it's going to decide that it wants

  • to send it along this channel.

  • So like the moves we saw earlier, what's going to happen

  • is this will create a shallow copy at runtime.

  • And it'll transfer ownership of the value from the child

  • onto the channel itself.

  • Then it's key that this ownership is not

  • transferring to another thread, but rather,

  • to a channel itself.

  • So the owner of this data is now the channel and not the threads

  • themselves.

  • So we come back to the parent, which

  • decides they're the ones to receive a message,

  • where we're going to take this shallow copy of the data

  • from the channel, transfer ownership over to the parent.

  • And now the parent can have access to it

  • and do whatever it wants to it.

  • Now, some key things to get away from this

  • are the child no longer has access to the vector.

  • Because we have moved the data onto the channel,

  • there's no way for the child to continue to modify it

  • or to mutate it.

  • It's passed ownership.

  • It's relinquished ownership.

  • Whereas on the parent side, we can

  • be guaranteed that once we've received a message, that we

  • contain ownership.

  • And there are no other outstanding aliases

  • or references.

  • We know that we are the only way people who can

  • access this data at this time.

  • And then the other thing is that I

  • want to emphasize this is all a shallow copy happening here

  • at runtime.

  • So the data inside this vector itself was never moved around.

  • They never copied it to other locations.

  • All we had to do was move this point

  • around into the same data.

  • So the next paradigm that I want to talk about

  • is shared read-only access.

  • Typically, message passing is great for a lot

  • of various algorithms and various ways

  • to set up a system.

  • But a lot of times, you don't want

  • to have to send data around.

  • You want to send it once, and then everyone

  • has access to this larger array, or large image,

  • or whatever you want.

  • And what Rust has for this is something that we call Arc.

  • And Arc stands for Atomically Reference Counted.

  • And what this means is that it's a reference count on top.

  • And then we will modify the reference count atomically.

  • And the actual memory representation for this pointer

  • is we store the reference count inline

  • with the rest of the data in the Arc itself.

  • So the data fields of this vector

  • found at the end of the Arc pointer.

  • But all we have to do is tack on this reference count.

  • So this is the whole zero-cost aspect of-- the Arc

  • isn't giving you any extra layers of indirection

  • than you're already asking for.

  • But one of the key aspects about Arc is when you create it,

  • it took ownership of the value.

  • So this Arc consumed ownership of the vector when

  • it was created, which means that it

  • knows it has a static guarantee that there are no aliases.

  • There's no mutable references.

  • There's no one else that could possibly access this data

  • when it was created.

  • So the Arc has complete control over how it gives you

  • access to the internal data.

  • So being a shared read-only state primitive,

  • we're only going to allow shared references from this.

  • So from an Arc, we can get a reference

  • to a vector of integers.

  • And this, in a concurrent context, is immutable.

  • So we're totally safe from data races in this case.

  • Because there's no mutation going on here.

  • We're only allowing aliasing, but we're

  • forbidding the mutation aspect.

  • And so the key idea here is that we

  • can use ownerships to understand that we control access.

  • And then using borrowing, we can control what kind of access

  • we give you in the Arc.

  • So we're not giving immutable access.

  • You can't mutate, which is kind of the best practice in shared

  • state concurrency.

  • But it's not necessarily enforced at compile time.

  • And you might accidentally have some mutation occasionally.

  • But in Rust, it's totally forbidden at compile time.

  • So the next thing that I want to talk about

  • is locked mutable access.

  • This is when you have some mutexes use

  • some extra synchronization to prevent threads

  • from running in simultaneously.

  • So we'll take a look at a small example here where all we do

  • is we take a mutex, we lock it, push some more data onto it.

  • And the first thing you'll notice is this type parameter.

  • We have a mutex of a vector of integers.

  • You don't see this a lot everywhere else.

  • This is kind of following the paradigm

  • of you locked in a not code.

  • So typically, you'll see mutexes around a certain block of code.

  • And then you'll access some data inside of it.

  • And it's this implicit assumption

  • that you'd better access that data only inside those mutexes.

  • Because if you access it outside,

  • you might be causing some data races.

  • So in Rust, this is a static guarantee that you're given.

  • You are given a mutex which is explicitly protecting

  • the data that it is containing.

  • And then like Arc, we have complete control

  • over the ownership of the data when it was created.

  • So the mutex is only allowing you access once you've actually

  • locked the mutex.

  • So in this case, when we lock the mutex itself,

  • we're getting a sentinel.

  • We're getting kind of a placeholder value

  • which serves as a smart pointer, in a sense,

  • to the data that's inside the mutex.

  • So through this, we can mutate it.

  • We can read it.

  • So in this case, we're going to push some data onto it.

  • But the key idea here is that we can only access the data

  • through this guard.

  • And we can only get this guard if we've locked the mutex.

  • So this is where we are protecting this data.

  • And you can only ever access it if you acquire the mutex,

  • preventing the data races that would happen if you access it

  • outside.

  • And the other great thing about these mutexes in Rust

  • is that we know exactly when this lock is

  • going to be released.

  • So, because we have deterministic destruction like

  • in C++, when this guard goes out of scope,

  • it's going to automatically unlock the mutex that it was

  • associated with.

  • So another key aspect here is that if I try and borrow

  • the data from this guard, if I try and take

  • a borrow out of the Rust lifetime system,

  • like I was talking about earlier with these scopes,

  • can prevent us from having that reference outliving

  • the guard itself.

  • So we can make sure that all references, even shared

  • references which you'll pull out from this guard,

  • are only ever accessible while the mutex itself is locked.

  • So we can use all these ownership and borrowing

  • guarantees that Rust gives us to make sure

  • that because the datas only have access in lock,

  • we totally prevented data races here.

  • And the last thing that Rust will give us

  • is these extra tools to check whether types

  • are sendable across threads, for example.

  • This is one example of a trait that Rust gives.

  • So what this function is saying is

  • that I'm a function which can transfer any type which

  • is sendable to other friends.

  • And then an example of this is Arc, like we saw earlier,

  • is indeed sendable to another thread.

  • But its sibling, Rc, which stands for reference counted

  • is not sendable to other threads.

  • This is a key difference where Rc,

  • the modifications to the reference count,

  • are not done atomically.

  • They're much, much cheaper.

  • So it's much faster for all these reference counts of Arcs.

  • But if an Rc were sent to another thread,

  • because it was a non-atomic mutation,

  • you could have a data race.

  • So at compile time, we're able to say

  • in Rust that Arcs are sendable, but these Rcs are not.

  • And this is in contrast to C++'s std::shared_ptr,

  • if you're familiar with that.

  • They don't actually know whether this class is going

  • to be sent across threads.

  • So you have to pessimistically assume that it will be.

  • So even if you know for a fact that your references are not

  • actually escaping the thread you're using them in,

  • you still have to pay for this atomic reference count

  • overhead.

  • So what this means is that in Rust,

  • you can pick and choose whether your primitives

  • are going to be thread-safe or not thread-safe.

  • And if they're not thread-safe, you

  • can be guaranteed at compile time

  • that you're still not going to have any data races.

  • These Rc pointers will never escape the thread

  • that you're using them in.

  • And if you do actually need that, then you

  • can yourself opt-in to the atomic reference counting

  • overhead, which will be necessary.

  • So that's an example of how using ownership

  • and using borrowing, we can use these tools to give us

  • all these great concurrency primitives,

  • all these primitives like message passing,

  • shared-state concurrency, mutex.

  • They're all built into the standard library of Rust.

  • They're not actually in the language

  • itself, which allows us to iterate on them, to make tweaks

  • to them, and basically extend the language and then grow it

  • a little bit to beyond just the concepts of ownership

  • and borrowing.

  • And I want to go into now how these

  • are implemented, what's actually going on under the hood.

  • Because Arc is giving you this facade of shared ownership,

  • actually.

  • There are multiple references that can access this data.

  • So this strict concept of ownership

  • or this strict concept of borrowing doesn't always apply.

  • And this is where unsafe Rust comes into play.

  • So Rust has this notion of unsafe code.

  • It's a block of code delineated saying that unsafe operations

  • can happen within this block.

  • And this is useful for doing things

  • like talking to other languages, for like CFFI bindings.

  • Because the compiler has no idea when you call a function,

  • what it's actually going to do on the other side.

  • So it has to assume pessimistically

  • that something memory unsafe is going to happen.

  • You have to opt-in to saying, no, it actually won't.

  • And it's also great for what I was

  • saying earlier about building Arc or building Vec.

  • We can use unsafe code to build up new abstractions

  • in the language.

  • And the key idea here is that because we have

  • told the compiler, trust me.

  • I know what I'm doing within this unsafe block,

  • I will maintain memory safety myself.

  • We can make this safe abstraction around it.

  • This is the crux of Rust, is building

  • these safe abstractions, which might

  • have small, unsafe internals.

  • But the safe abstraction is what everyone relies on

  • as part of the public interface.

  • So the way this typically works is

  • the safe abstraction, the safe layer,

  • will do some dynamic runtime checks, like making sure

  • that indexes are inbound.

  • Or making sure some other dynamic invariant that's

  • very tough to reason about at compile time.

  • And then it's going to add on top

  • of that the guarantees it gets from ownership and borrowing,

  • these static guarantees of, if I owned it,

  • I'm the only one, or share references I can only

  • read from, and mutable references I

  • can either read or mutate.

  • But it's totally unique.

  • And using all these static guarantees,

  • you can bend Rust a little bit in the ways

  • that you'd like with unsafe code.

  • And so you might be thinking, well, if we have unsafe code,

  • haven't we just made Rust's memory unsafe?

  • Haven't we just broken down this idea

  • on saying Rust is a safe language?

  • And it turns out the way this works in practice

  • is that ownership/borrowing cover almost all use cases I've

  • ever seen in terms of how you would architect a system

  • or how you would design it.

  • This concept of, I can either borrow it in a shared fashion

  • or borrow it in a mutable fashion.

  • It encompasses, well, maybe with the little tweaks of what

  • you're already doing today.

  • This is already the best practice

  • of how you're accessing data.

  • And this is just codifying it at compile time.

  • And as a testament to this, Cargo,

  • which is Rust's package manager--

  • I'll talk about that in a second-- and Skylight,

  • like I said earlier, have zero unsafe blocks.

  • There's no unsafe code in these projects,

  • except for [INAUDIBLE], which is kind of assumed.

  • And these are fairly substantial projects

  • that are doing some pretty crazy internal things, things

  • with ownership, things with borrowing.

  • And they haven't felt the need to fall down

  • to this unsafe code to break any invariants.

  • So they've been able to fit entirely

  • within this programming model.

  • But the great thing about unsafe,

  • like I was saying with Arc, is we can extend the programming

  • model.

  • If you do find yourself needing to drop down to unsafe

  • or to have a little bit of unsafe code, what it means

  • is you're probably writing a very small primitive, a very

  • small thing that you can very easily reason about

  • and say, yeah, this is safe.

  • The compiler can't totally reason about this.

  • But is a safe extraction.

  • And using that, you can add new primitives.

  • Like we have these things called Cells, or RefCells, x

  • or Mutexes, or Arcs, all this fun

  • stuff in the standard library.

  • And you could build on top of what

  • we're given by ownership/borrowing by bending

  • ownership/borrowing just a little bit to give us

  • these new set of primitives that we can leverage and take

  • advantage of.

  • So that's ownership/borrowing in a nutshell, how we can

  • use it to build concurrency.

  • And then how we can use all of these guarantees

  • to use unsafe code to build new primitives.

  • And I'm going to talk a little bit now

  • about using Rust today, just a quick tour of some

  • of the stuff that we have and the tools that Rust provides.

  • One of the primary ones is Cargo.

  • So I said this earlier, but Cargo is Rust's package

  • manager, where what it does is it manages

  • all of your dependencies.

  • It's very similar to Ruby's Bundler.

  • And it'll take care of building our dependencies,

  • building transit dependencies making sure

  • that all your system libraries are in place.

  • It'll take care of all this logic,

  • allowing you to get to writing your application.

  • So one of the great things about this

  • is we can guarantee reproducible builds.

  • So this is a very important part of Bundler,

  • if you're familiar with that, with locked files.

  • And we took the same concept to Cargo,

  • where if I ever build a project I

  • can then be guaranteed, if it's successfully built,

  • that later in the future, I can build that project again.

  • I don't have to worry about getting all the same versions

  • of all the dependencies and making

  • sure all the transitive dependencies work out.

  • I can be very confident that it was the same set of code

  • will build again.

  • And then all the libraries today for Cargo are crates,

  • and they're all hosted on crates.io.

  • We have a very booming and active community today.

  • And this has been absolutely critical to Rust's

  • initial surge into the market.

  • A lot of Rust's initial success has been-- it's very,

  • very easy to put your code up on crates.io and to also use

  • everyone else's.

  • So even now it's quickly becoming the case

  • that if you have some pretty common use case, like gzip

  • or bzip or various compression, or XML, or parsing JSON,

  • it's already there on crates.io.

  • And it's very easy to pull those crates down, use them yourself,

  • browse documentation, and go through the whole using

  • everyone else's Rust code yourself.

  • And a little bit on Rust itself.

  • Rust itself recently reached the 1.0 stable status

  • this past May.

  • So this is a very large milestone for Rust,

  • reaching the sustainable aspect.

  • We're no longer breaking the language.

  • We're no longer breaking the standard libraries.

  • You can guarantee that Rust is not changing in the future.

  • And coming along with this is this release training idea

  • where we have this pipeline of channels where

  • we have the stable channel, the beta channel,

  • and the nightly channel where we promote these

  • at a regular cycle, kind of like web browsers.

  • And we'll see that we have a whole stabilization

  • pipeline for features where the new features,

  • we can iterate on very quickly in nightly.

  • And you can have access to them basically

  • immediately, as soon as they're implemented.

  • You can give us feedback.

  • We can fix bugs.

  • We can iterate on them.

  • Or we prevent them from leaking into the beta

  • and the stable channels.

  • But then once a feature is actually

  • stabilized in the nightly channel,

  • it's a very short amount of time before it actually

  • gets into beta, gets into stable,

  • and everyone can start using it.

  • So the key aspect here is "stability without stagnation."

  • When you're using Rust, you can-- we're not done with Rust.

  • We want to keep adding on new features.

  • We want to keep adding new standard library APIs

  • or new libraries themselves.

  • So over time, we're going to be adding these things

  • to Rust itself, but while at the same time,

  • giving you a very strong stability guarantee.

  • We're not going to be breaking code willy-nilly.

  • We're not going to break into the language itself.

  • All right, so some of the high-level conclusions of this

  • talk that I want to make sure you walk away with is that Rust

  • is combining these high-level features of JavaScript and Ruby

  • with the low-level control of C and C++.

  • So it's kind of unioning these two together

  • with both safety and control.

  • And Rust can give you very strong safety guarantees

  • beyond what a garbage collector can give.

  • So, for example, we have deterministic destruction.

  • We forbid all data races.

  • We forbid iterator invalidation.

  • All of these happen for free at compile time.

  • And you can basically program very confidently.

  • You can have fields concurrency.

  • You don't have to worry about these kinds of bugs happening

  • in your code because Rust is providing them

  • all at compile time.

  • All right.

  • And that's what I have for today.

  • So thank you for coming, and are there any questions?

  • Yes.

  • [APPLAUSE]

  • AUDIENCE: I [INAUDIBLE] for two weeks.

  • And you advertise it as a systems programming language.

  • And one problem I've had is it must

  • be that the standard library assumes allocation

  • of the parents, basically.

  • So are you at all interested in making

  • a [INAUDIBLE] standardizing library

  • of [INAUDIBLE] and other facilities which

  • return allocation values?

  • Or you're simply [INAUDIBLE] how to use this [INAUDIBLE]?

  • ALEX CRICHTON: I am very much interested in this, actually.

  • [? RAIF LEVINE: So ?] could you repeat the question, please?

  • ALEX CRICHTON: Yes.

  • So the question was, we have a standard library today.

  • We advertise ourselves as a systems programming language,

  • but the standard library assumes that allocation succeeds.

  • It assumes it does not fail, so it'll

  • abort the process if it does.

  • So he's wondering whether we have

  • any plans to kind of expand this where we can have optional--

  • like, we can say whether allocation is failed

  • or not by having the actual return value.

  • And I can decide what to do with that.

  • And the answer is yes.

  • I very much want to be able to do this.

  • So right now, we have this distinction where

  • we have the standard library.

  • And underneath it, we have a whole facade

  • of libraries at the core, which is actually libcore.

  • And libcore is this library which doesn't actually

  • assume allocation at all.

  • It can't even allocate.

  • And then once we eventually hit a point where

  • we do assume allocation succeeds,

  • we can build more things on top of that.

  • So this will all start by libcore

  • itself will be stabilized.

  • We want to export that as part of the stable channel of Rust

  • itself.

  • And that will give you access to most of the language,

  • not the collections, not the pointers.

  • And then the story there is a little bit more

  • murky in the future.

  • I very much do want an Arc where the only difference

  • is the new method that says whether it failed to allocate

  • or not.

  • We don't have concrete plans and designs, just how

  • to go in that direction.

  • Because the way to start off is to get the core aspect

  • worked out.

  • But it's definitely coming, and it's definitely use case

  • that Rust very much wants to fill into.

  • Yes.

  • AUDIENCE: So there are a number of other tech systems

  • that deal with these type of things,

  • like linear and [INAUDIBLE].

  • And I was wondering if you could tell us about what

  • full programming languages or [INAUDIBLE] systems you looked

  • at when shooting this design.

  • And what you liked, and what you disliked.

  • ALEX CRICHTON: Yeah.

  • So the question is, there's a lot of type systems like Rust

  • that's linear affine type systems

  • and what languages and type systems

  • we've looked at to influence Rust design,

  • how it's come over the years.

  • And so, like I say, I personally have not

  • been that influential in the design of the core type system.

  • But from what I've heard, it's definitely linear affine types.

  • There's lots of papers on that which

  • are very influential on Rust itself,

  • especially when determining the linearity references

  • and things like that.

  • But I think Cyclone might have been one of the larger

  • languages that we've drawn from in terms

  • of drawing the experience from and seeing

  • what's happened there.

  • But overall, it's actually drawn a lot from actual languages

  • like C and C++, like going back to our fundamentals of seeing

  • what it actually means to have no runtime,

  • kind of in that aspect.

  • So we've drawn things from other places.

  • But those are the big ones that I know of.

  • And I'd have to refer you to others

  • to go into more detail about some of the academic research

  • going on there.

  • Yes.

  • AUDIENCE: Would you a [INAUDIBLE] what was

  • to get about the same as us?

  • ALEX CRICHTON: I'm sorry?

  • AUDIENCE: [INAUDIBLE] in C++, can you at least take C++

  • by compile and changing [INAUDIBLE] to get about

  • the same as us when we're equal in everything else?

  • ALEX CRICHTON: So the question is,

  • can we bolt safety onto C++?

  • Can we tweak the standard library, tweak the compiler?

  • Can I get these safety guarantees?

  • Maybe check out something on safe primitives

  • so we get a safe language.

  • And the key, I think here is, we've seen with C11 and C++ 14,

  • these new standards.

  • They have things like unique pointer.

  • They have shared pointer.

  • They have lock guards.

  • They have all this great new stuff.

  • But the problem is you can still misuse it.

  • It's still very easy to use these standard primitives,

  • which are pretty safe.

  • But you can use them in unsafe ways.

  • AUDIENCE: Plus they can't [INAUDIBLE] on the [INAUDIBLE].

  • It can break it, and it's a lot of problems.

  • ALEX CRICHTON: Yes.

  • Those things, they have limits of backwards compatibility.

  • And if we could break it, we could actually

  • fix a lot of things.

  • And it's kind of an expense at some point.

  • So if you're moving C++ in a direction where

  • you're stripping away all the unsafe features,

  • stripping away all the stuff.

  • Than at what point do you actually

  • create a new dialect of language?

  • And from what I've seen in C++ and Rust,

  • is the move semantics are very radically different in Rust

  • as they are in C++.

  • There's no move constructors.

  • There's no copy constructors.

  • There's no extra stuff happening there.

  • And the other major thing is references.

  • Dealing with lifetimes, I think, is just something you

  • fundamentally cannot bolt onto C++ and still have the same

  • language.

  • And those are very unique to Rust.

  • And there's the underpinnings of the safety itself.

  • So those two aspects tied together, I feel,

  • we could add them to C++.

  • We can kind of tweak C++ and break it in various ways.

  • But at some point, you really have just

  • created a new language at that point.

  • But definitely today, in terms of backwards compatibility,

  • there's just no way that we could actually bolt on safety

  • to C++.

  • You can have all the static analysis you want.

  • We've actually talked to tons and tons of people

  • in various aspects of industry.

  • They all have huge amounts of static analysis.

  • But everything falls down at some point.

  • It catches 90%, 95% of bugs.

  • But that 5% leaking through is still

  • going to have memory unsafety, security vulnerabilities,

  • and all that good stuff.

  • Yes.

  • AUDIENCE: [INAUDIBLE]?

  • ALEX CRICHTON: Yeah.

  • So the question is, one of the great things about an existing

  • language is we already have tons of libraries.

  • We have decades of code, decades of experience writing all this.

  • So how's Rust going to do all this?

  • How's Rust going to get these libraries' bootstraps,

  • get this up and running, how's the community running?

  • So what I would say to that is this

  • is where crates.io has been absolutely critical to having

  • it there for Rust 1.0.

  • With crates.io, it's incredibly easy to share libraries,

  • to share our code.

  • We reduced the barrier entry to writing this code,

  • and publishing it, and getting everyone's access to it, so

  • a very, very small amount.

  • And this is coupled with there are not many other systems

  • languages-- I mean, you would never find a package manager

  • for C and C++, like to actually build the C and C++ itself.

  • It was just uniform across all projects.

  • So Cargo has been also absolutely instrumental

  • here in terms of making it very easy to depend

  • on everyone else's code.

  • So it's kind of reducing friction as much as possible

  • and making it easy to jump in there.

  • So we ourselves are trying to lead

  • the-- we've led the way in terms of stabilization

  • of the standard library.

  • We have set some common idioms for the language,

  • some common interfaces.

  • But the libraries themselves, we unfortunately

  • don't have the manpower to invest

  • in these very, very high quality, very large libraries

  • that one might expect.

  • So we have implementations in existence today,

  • but they're kind of in progress.

  • They're works in progress.

  • They're all kind of an open source.

  • So it's definitely a downside.

  • This is something that will take time to flush out these common

  • libraries you would expect in C and C++ in Rust itself.

  • But a lot of large ones are either very much underway,

  • feature complete, or they're on their way coming.

  • That answers your question?

  • Yes.

  • AUDIENCE: So I'll keep continuing

  • on that line of thought.

  • I know Servo has been felt throughout the closest thing

  • to Rust.

  • And so, my question is really about how big

  • you intend to [INAUDIBLE].

  • So in a project like Chromium, or I'm

  • sure Mozilla lets you have-- there's like base, which

  • has a lot of these Chromium abstractions,

  • really standard library type stuff

  • that a really big part of my web browser needs to use.

  • So does Servo define the sorts of abstractions for itself.

  • Or do you intend the standard library for Rust

  • to be the standard library for Servo?

  • Or what's the story there?

  • ALEX CRICHTON: Yeah.

  • So the question is, where do we see the standard library going?

  • Is it going to be a very large standard library

  • with tons and tons of things?

  • For example, it's very heavily influenced by Servo

  • in terms of their needs, and how those two are

  • going to play out, and whether Servo

  • has its own standard library.

  • So the approach we have taken with the standard library today

  • is fairly conservative.

  • So it's not a very large standard library.

  • I would not use the adjective "batteries included"

  • to describe it.

  • And the reason for this is, having

  • gone through the whole stabilization process,

  • there just wasn't enough that we felt comfortable stabilizing.

  • Did I do something wrong?

  • [? RAIF LEVINE: Yeah, ?] the slides aren't projecting,

  • but I think that's OK.

  • ALEX CRICHTON: OK.

  • So Servo has, at this point, like 150 crates

  • that it's depending on, 150 various libraries that

  • are being built through Cargo.

  • So they're taking the same aspect of they're

  • probably not going to create one monolithic standard library,

  • but rather have lots of little components here and there.

  • So the role of the Rust standard library

  • is going to be to define the common interface

  • for the entire language, so iterators, vectors,

  • hash maps, the option type, the result type.

  • These common abstractions you see

  • in basically 99% of Rust code all over the place, that

  • belongs in the standard library.

  • Once you start getting above that,

  • like some various futures or various weird concurrency

  • primitives, those might be outside the standard library,

  • but in their standalone creates.

  • But we still officially support them.

  • So I suspect the trajectory over time

  • is to stay with a relatively conservative standard library,

  • but a very diverse set of crates that are very small

  • that just build on top of the standard library

  • and kind of work like that.

  • Yes.

  • AUDIENCE: So my question basically

  • is, all the codes I work on are kind of unique in a way

  • that they're not many that are interested in, right?

  • I mean, but there's this core library in C++ usually that

  • everybody's using.

  • And my question is then how are you doing things around each--

  • basically, our language is wrapping stuff.

  • And how will you handle basic safety in such a department?

  • ALEX CRICHTON: So the question is

  • about how Rust interacts with other languages

  • and how we deal with the safety around that in terms of talking

  • to big projects elsewhere.

  • So this is a very important aspect of Rust.

  • We know that the world is not going to be

  • rewritten in Rust overnight.

  • We've got to deal with existing libraries.

  • So Rust has the FFI boundary of Rust,

  • allows you to just kind of call on external languages.

  • It's just a call instruction.

  • There's no overhead from this.

  • It's exactly what you'd expect from C. So an example,

  • this is Cargo, the package manager, uses libgit2,

  • which is a library written in C for managing Git repositories.

  • And we just call straight out to that.

  • And then to maintain the safety of Rust

  • itself is where ownership, and borrowing, and lifetimes come

  • into play.

  • So that's where you can create a safe abstraction around calling

  • these unsafe libraries.

  • So the C APIs typically have a method of saying,

  • here's when you create the resource.

  • And then you deallocate it when it goes out of scope.

  • So for example, the destructor for that type,

  • we call the free function.

  • And then when you access an internal value,

  • you know you have the ability to tie the lifetimes together.

  • So in return to them, it says, this is only valid

  • while the struct is valid.

  • So you can construct-- you can add, basically,

  • the Rust type system on top of an existing C API.

  • So you can kind of use these static guarantees of Rust,

  • this ownership/borrowing, mutable references

  • being unique, all that good stuff to maintain the safety.

  • You have to be the one that actually creates that API

  • and creates the safe interface to it.

  • Does that answer your question?

  • Yes.

  • AUDIENCE: How do you manage quality and safety

  • of the crates that are posted on [INAUDIBLE]?

  • ALEX CRICHTON: So the question is,

  • how do I manage the quality and the safety of the creates

  • in the crates.io?

  • And this is actually a very interesting question.

  • So the quality itself, we have a small set of crates

  • that we curate.

  • They're hosted in the Rust lang organization itself.

  • So I would not describe them all as incredibly high quality.

  • But we maintain them.

  • We fix bugs.

  • We push updates.

  • We have a lot of continuous integration for them.

  • But in general, you don't actually

  • have this kind of guarantee of quality.

  • You don't know the tests are running.

  • You don't know that it works on your platforms.

  • And this is stuff that we would like

  • to expand to in the future.

  • But it's not something that we're actively pursuing

  • [INAUDIBLE].

  • We're not going to give C [INAUDIBLE] the whole Rust

  • community.

  • We'll very, very strongly encourage it, use Travis,

  • or use AppVeyor, or whatever open source solution you want.

  • But you don't have the stronger NT coming in.

  • You have to be able to Google around

  • the libraries ahead of time.

  • And it's the same thing with safety.

  • There's not exactly a badge on crates.io that says,

  • this crate has unsafe.

  • You can't use a [INAUDIBLE].

  • If you use it, you might have unsafe code.

  • So the community is very active in terms of these kind

  • of high profile libraries.

  • If there's unsafe code inside, and it actually

  • is legitimately unsafe, then bug reports will be opened.

  • And it'll be fixed very quickly.

  • But overall, we don't actively curate

  • crates in terms of auditing them for security,

  • or auditing them for quality, or anything like that.

  • Yeah.

  • AUDIENCE: What about other [INAUDIBLE]?

  • What if the person who was maintaining some crate

  • that everybody needs gets hit by a bus?

  • ALEX CRICHTON: Oh, so the question

  • is, what do we do about ownership

  • of crates in terms of what if the original owner just

  • disappears?

  • And we have systems for-- we can transfer ownership of crates

  • here.

  • We can transfer ownership between owners.

  • Or, I mean, if someone's random crate ends up disappearing,

  • then we probably don't take ownership of it.

  • We're not holding ourselves responsible for all libraries

  • and crates on crates.io.

  • In the back.

  • AUDIENCE: [INAUDIBLE]?

  • So what is the current state of [INAUDIBLE]?

  • ALEX CRICHTON: The question is, what does the performance

  • of Rust look like?

  • Because we're using LLVM as a back end, kind of relative

  • decline?

  • And I can say, we're basically on par with C++.

  • We don't like disabling the optimizations,

  • or we don't have any other fancy trickery going on there.

  • But if you use Rust, you're going to get basically what you

  • would get in C++.

  • I don't know, is there a remote question?

  • I don't know if it's switch stream.

  • AUDIENCE: Yeah.

  • You were doing a lot of type checking

  • to get these guarantees.

  • Is that drastically increasing your compile time?

  • And is that scaling significantly to project size?

  • And is there a point where project scale gets unwieldy,

  • especially for iterative development?

  • ALEX CRICHTON: I'm not going to repeat this

  • because it was online.

  • But anyway, yes, so this is an interesting question.

  • And the compilation model for Rust is fairly different than

  • C++.

  • So in C++ compile one object at a time.

  • And you include all your parse headers.

  • But Rust, it's actually essentially a library

  • at a time.

  • It's a crate at a time.

  • So when you compile a Rust crate,

  • you're compiling essentially all the C++ files at the same time,

  • kind of in similar models.

  • So in that sense, the compile time for-- like the incremental

  • compile time for one Rust library is going to be higher

  • than a C++ library, because you have to recompile the entire

  • library.

  • But in terms of type checking, it's fundamentally way faster

  • than C++, because we use traits in our generics.

  • And we do not need to type check them

  • after we've instantiated them.

  • So we can type check everything once.

  • And then we don't have to worry about it ever again.

  • And when you depend on an upstream create--

  • so, for example, if I include some header file from a C++

  • library, I don't have to re-type check that every single time

  • you run the compiler.

  • So compile times are a little bit

  • of a problem today in terms of because we're

  • compiling all these big crates.

  • So there's a lot of efforts underway.

  • Like even today, by the end of the year,

  • we're probably going to have incremental compilation,

  • pipeline compilation, parallel compilation, all

  • these great aspects.

  • But in terms of just the raw compile time which you'd expect

  • today, if you start from zero, Rust code will compile a little

  • bit faster than C++.

  • If you go in an incremental fashion,

  • C++ might be a little faster because we don't have

  • the incremental aspects.

  • And I think there's one other aspect to your question.

  • Or did I hit all your points?

  • Oh, scalability, yes.

  • So scalability, I think it definitely

  • scales quite well today.

  • We haven't run into lots of problems.

  • Because the compile time for one crate can be a little high

  • we might have to break that library

  • into two separate crates.

  • But that's generally a good exercise to do anyway.

  • But Servo has not had problems with compile times that

  • would not be solved with incremental compilation,

  • for example.

  • AUDIENCE: Just for reference-- hi, I'm Chandler.

  • I work on [INAUDIBLE] in LDM.

  • But I wanted to let you know that the [INAUDIBLE] compiler

  • spends almost no time type checking C++.

  • It's not really measurable as part of C++ compile time

  • for us.

  • So type checking is never a compile time problem

  • that we've run into.

  • ALEX CRICHTON: Interesting.

  • AUDIENCE: Alex?

  • ALEX CRICHTON: Sure.

  • AUDIENCE: What's the largest Rust code

  • base you're aware of on the client and also on the server,

  • if any?

  • ALEX CRICHTON: So the two largest Rust code bases

  • I know of are Servo and Rust.

  • I don't know if you would-- in terms of a server client,

  • like an HTTP server or an HTTP client,

  • I don't think we have that large code base.

  • It's not quite the niche that Rust is filling right now.

  • So those would be the two.

  • I guess you could qualify Servo as a client, as opposed

  • to a server.

  • But in terms of the largest server,

  • I know crates.io was entirely written in Rust.

  • But that would be the largest one that I personally know of.

  • You had a question?

  • AUDIENCE: How good is your debugging support?

  • ALEX CRICHTON: The question is, how good

  • is the debugging support?

  • AUDIENCE: Yes.

  • That's right. [INAUDIBLE] debugging, and debugging

  • of the [INAUDIBLE], and in cases where it sometimes

  • has a few bugs.

  • ALEX CRICHTON: Sure.

  • So the story here is, if you can debug C++, you can debug Rust.

  • So we use LLVM as a back end.

  • And we have debugging go through that.

  • We have DWARF debug info.

  • We have GDB integration in terms of pretty-printers.

  • So if you can debug C++, you can debug Rust.

  • So whatever you would expect to do there,

  • you can just do the same thing.

  • Because as we're using LLVM as a back end,

  • it's all just native object files.

  • We're using the standard system linker, and all

  • that good stuff.

  • So the standard C++ tools-- and this applies to profiling,

  • testing.

  • All these kinds of various analyses

  • will apply to Rust as well.

  • AUDIENCE: One more question

  • ALEX CRICHTON: One more question?

  • All right.

  • Thank you so much.

  • [APPLAUSE]

[? RAIF LEVINE: All ?] right.

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it