The Rust Programming Language

The Rust Programming Language


[? 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]

37 thoughts on “The Rust Programming Language”

  1. Thanks very much for the great and neat presentation followed Alex Crichton.Your presentation is much appreciated.

  2. Rust is a very, very promising language, and, after some reading, it seems to me that it lacks any glaring deficiencies – unlike let's say Golang (with its horrible type system and non-extensible nature).

    There's certainly space for sane C++ replacements.

    Great presentation of Rust's distinguishing characteristic (ownership/borrowing), although I'd also love an overview of the object system and the very interesting functional features of the language.

  3. I will give it a try in a new project. I will rewrite Lucene (the java, then c++ full text search engine) and add it to a special database for a new AI enabled webscrapper. I think this is a good project that can need lots of multithreading help and thats where i expect the most help from the code. Most important is that i can integrate it with C easily.

  4. I have had an interest in programming for a while and I really like what I'm seeing here in terms of control and structure. My question is if RUST is a good first programing language for a beginner like myself to learn.

  5. Thank you for the video, which I probably watched in 4 minutes by pressing the right key every second. That's about the time I have.

  6. I have never listened to a great tutorial, precise, simple, articulate, summarized and fully descriptive as well as comprehensively detailed like this one. I have loved the programming language since then. Thanks for the awesome video.

  7. Summing up this lesson:

    The problem in memory management is
    aliasing (2 references to the same variable), and
    mutation
    …happening at the same time.

    To solve that, in Rust a variable can be passed in 3 ways:

    1. "var": *Ownership*. Ownership of var is immediately passed to the called function, and the caller cannot use var anymore, it is completely forgotten. Trying to use var in the caller results in "var has been moved" compiler error.
    2. "&var": *Shared borrow*. The caller can send the variable to multiple consumers, but mutation is forbidden, the callee can only read var but not mutate it. (But mutation is allowed in certain controlled circumstances.)
    3. "&mut var": *Mutable borrow*. The callee, alone, can mutate the variable. The variable is returned through the stack and the caller gets control over the variable again.

Leave a Reply

Your email address will not be published. Required fields are marked *