
Fearless concurrency with Rust - steveklabnik
http://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html
======
Animats
Rust's ownership model, and the borrow checker that enforces it, are a major
breakthrough in language design. It's so simple, yet it solves so many
problems.

This is what Go should have done. Then Go's "share by communicating, not by
sharing" would be real, not PR. Go code often passes references over channels,
which results in shared memory between two threads with no locking. In Rust,
when you do that, you pass ownership of the referenced object. The sender can
no longer access it, and there's no possibility of a race condition. A
successor to Go with Rust ownership semantics has real potential. Go has a
simpler type system than Rust, and seems to be well matched to the back-end
parts of web-based systems.

~~~
chimeracoder
First, please don't compare Go and Rust. They are completely different
languages with completely different target use cases. Go has very different
design goals than Rust, so very little that Rust does would actually be
possible in Go, and vice versa.

> Go code often passes references over channels, which results in shared
> memory between two threads with no locking

Having a complex type system would cut into compile times (an explicit primary
design goal, to the point where the Go compiler must be written to read each
file exactly once[0], no more).

As for the code you're referring to, Go is not designed to be a language which
prevents you from shooting yourself in the foot with provable code. It's
designed to be a language which makes it reasonably easy to be sure you
haven't, as long as you follow the general idioms and best practices. What
you're describing here is definitely not one - I can think of a few instances
in which pointers might be reasonably passed over a channel, but they're few
and far between.

Also, I would think it's pretty obvious that once you've sent something over a
channel you shouldn't try and write to it anymore[1]. Do you have an example
of the code you're referring to?

[0] well, technically "at most once", since some files can be skipped
entirely.

[1] I'd need to think about this, but I don't think it'd be difficult to
detect this statically[2] at compile-time and enforce that stack-allocated
rvalues into channels are never used again in the same scope. It's definitely
possible to extend `go vet` to handle this, and it may even be possible to
write this in as a compiler error in a future version of Go.

[2] Incidentally, one of the reasons that it's so easy to do reliable static
analysis on Go code (compared to other languages) is that the grammar is
incredibly simple - it's almost entirely context-free, which is very rare
among non-Lisps. Having a more complex type system usually requires at least
some additional syntax to along with this, which means you'd have to start
sacrificing this design goal as well in order to create a more elaborate type
system.

~~~
pcwalton
> (an explicit primary design goal, to the point where the Go compiler must be
> written to read each file exactly once[0], no more).

Same in Rust. Thanks to the module system, the Rust compiler never rereads a
file more than once.

> [1] I'd need to think about this, but I don't think it'd be difficult to
> detect this statically[2] at compile-time and enforce that stack-allocated
> rvalues into channels are never used again in the same scope. It's
> definitely possible to extend `go vet` to handle this, and it may even be
> possible to write this in as a compiler error in a future version of Go.

That trivially fails to solve the problem, due to aliasing.

> [2] Incidentally, one of the reasons that it's so easy to do reliable static
> analysis on Go code (compared to other languages) is that the grammar is
> incredibly simple - it's almost entirely context-free, which is very rare
> among non-Lisps. Having a more complex type system usually requires at least
> some additional syntax to along with this, which means you'd have to start
> sacrificing this design goal as well in order to create a more elaborate
> type system.

Rust's grammar is also context-free, as far as I know.

Your proposed static analysis is not reliable. The "more complex" type system
in Rust exists precisely _so that we can do more reliable static analysis_.

~~~
chimeracoder
> > (an explicit primary design goal, to the point where the Go compiler must
> be written to read each file exactly once[0], no more).

> Same in Rust. Thanks to the module system, the Rust compiler never rereads a
> file more than once.

As I said _right_ at the beginning of my post, I'm _not_ trying to compare
Rust and Go directly, because I don't think that's meaningful. I'm explaining
why these particular features would be difficult to incorporate into Go. Note
that I never said that Rust reads a file more than once, or that Rust's
grammar is not context-free. In fact, the word "Rust" doesn't appear anywhere
in my comment at all except in that very first paragraph.

I love talking about PLT and would otherwise be interested in having a
discussion about static analysis and hearing why you think it would not solve
the problem, but I have to say, it's both frustrating and discouraging to post
an in-depth response and then get downvoted twice, with the only reply being
one which very clearly ignores the very first line of my entire response.

~~~
pcwalton
> I would otherwise be interested in having a discussion about static analysis
> and hearing why you think it would not solve the problem

Stack allocation isn't in the semantics of Go, so that's a pretty weird thing
to use a basis of a static analysis. It's also not sound because of interfaces
or closures. You would want something more like "fully by-value data with no
pointers in it, no interfaces, no closures".

~~~
brandonbloom
Not to mention no arrays or slices! This burned me pretty hard during my first
week with Go.

------
carllerche
Rust has definitely been a pleasure to work with. I have been experimenting
with a Future & Stream [1] abstraction in Rust that would allow easily
describing complex concurrent and async operations and to allow easy
composition, not unlike ES 6 promises.

The interesting thing is that, thanks to Rust's ownership system, it is easy
to discover when handles to a future value go out of scope. This allows the
library to reuse internal resources to make the overhead of using the library
as light as possible.

For example, a Stream is modeled as a sequence of futures (more specifically a
future of the next value and a new stream representing the rest of the values,
kind of like lazy-seq in clojure but async), but instead of allocating one
future for each iteration of the stream, the same internal structure is reused
while still being able to expose this API.

[1]
[https://github.com/carllerche/eventual](https://github.com/carllerche/eventual)

~~~
jbandela1
Just looked at eventual. It seems really interesting. I looked at Rust's std
Future and was turned off by the fact that is blocking.

~~~
Ygg2
Rust is blocking, there was mio crate which brought non-blocking IO
operations.

~~~
carllerche
Well, Rust doesn't have any opinions regarding blocking or not. Currently,
std::io implements blocking IO, but as you pointed out, Rust allows libraries
to implement non-blocking / async paradigms.

~~~
steveklabnik
"Rust is so low-level that IO is a library concern, not a language concern,"
as I like to say.

~~~
Ygg2
But standard IO is blocking, correct?

But Rust being so low level to not care about IO is Awesome :)

~~~
steveklabnik
We're starting off by only having blocking IO in the standard library, yes.

------
jrapdx3
FWIW I thought the article was quite informative and well-written enough to
make the concepts easy to grasp. I'd agree with the opinion that Rust is
breaking new ground in an interesting way, and over the next several years I
wouldn't be at all surprised to see its ideas becoming quite influential.

The discussion points out something else that too often receives too little
attention. That is, the quality of documentation is extremely relevant to
product success. Sadly, software is more often than not poorly documented, not
only frustrating for potential users, but an unnecessary impediment to uptake
of the program.

It appears the Rust developers are serious about describing how Rust works.
That's a very favorable sign of their commitment to the project and speaks
volumes about dedication to grow the Rust community.

------
fny
Just a comment: I've noticed Rust literature generally errs on feeling a bit
dense and esoteric, which might make things less approachable for some. Then
again, that does depend on your target audience. This article doesn't make me
feel immediately "fearless" about concurrency, and I'm a fairly experienced
programmer and a fan of the language. Perhaps it would have been wiser to
start with an approachable example and then taken deep dive about all the
nuance from there?

Also, are there any plans to integrate tutorial-style guides similar to those
from Rust for Rubyists into the Rust book? There's something incredibly fun
and real about learning by example. It also would give the reader a chance to
read some idiomatic code.

~~~
steveklabnik
> Also, are there any plans to integrate tutorial-style guides similar to
> those from Rust for Rubyists into the Rust book?

I'd been waiting for beta to drop to do a re-organization of the TOC of the
book. You can see it on nightly here: [http://doc.rust-
lang.org/nightly/book/](http://doc.rust-lang.org/nightly/book/)

I've carved out a whole section, "Effective Rust", specifically for this kind
of thing. I'm looking for a better name than "Effective Rust", since that's a
very common book title, though. Don't want to be too greedy with that
namespace!

We do also have Rust by Example, but I admittedly don't give it as much love.
Once the book is done...

~~~
fny
Never new Rust by Example existed until today actually! You really should link
to it somewhere more prominent. I'm fairly far along, but would have loved the
example book had I know about it sooner.

~~~
steveklabnik
It was originally a community project, but was then abandoned for a while, and
then donated to Rust proper. As such, it's always been sort of secondary. I
plan on linking it more prominently once I've actually gone through it and
cleaned it up to my own personal standards, for now I just keep CI green. As
such, the text has gotten out of sync with the code in a number of places, and
I don't want to mislead anyone.

We do link it from [http://doc.rust-lang.org/](http://doc.rust-lang.org/) ,
but I guess that still says community...

~~~
fny
Fair enough: it's definitely not a good idea to promote something half baked.

It actually took me a while to find the link there just now. I don't think
"External Documentation" really imparts the right meaning. Perhaps something
like "Other Guides and Resources" would make more sense?

~~~
steveklabnik
Yeah, that was written when it wasn't an official project, it should move to a
different section now.

------
jewel
Sorry if I'm missing something obvious, but how do these locks stop a deadlock
from happening? To explain, say that I have some code that locks A, then locks
B, then does something, and another bit of code that locks B, then locks A,
then does its thing. There's a race condition that can happen where one thread
has A and the other thread has B.

This has bitten me so much that I avoid fine-grained locking when possible and
instead use a single global lock everywhere, and have new threads start in the
locked position, and then only selectively unlock the bits of code that I can
prove are thread-safe, or adding fine-grained locks only where absolutely
necessary as shown by benchmarking.

~~~
steveklabnik
> Sorry if I'm missing something obvious, but how do these locks stop a
> deadlock from happening?

This is the difference between a data race and a race condition. We can't
generally prevent deadlocks, I would imagine that's (like all race conditions)
is an unsolvable problem at the language level. Maybe some PhD will prove me
right or wrong, though...

~~~
rntz
Preventing deadlocks is not an unsolvable problem. Linear-session-typed
process calculi are guaranteed deadlock-free and race-free. (The connection
with Rust's ownership type-system makes me wonder if there might be some way
to backport this guarantee to Rust, but I suspect the connection doesn't go
far enough.) This comes at a price, however: certain process topologies are
impossible to construct, and there's no way to ask "give me the _first_
message to arrive on _either_ of these two channels" (no Unix `select()`
analogue). The latter in particular bugs me, because `select()` is a necessity
in practice.

In general, problems of "guarantee property X" can almost always be solved
with a sufficiently advanced type system, but at the cost of (a) inventing and
using a sufficiently advanced type system and (b) disallowing some perfectly
good programs.

~~~
Jweb_Guru
The safe subset of Rust (which is more-or-less linearly typed with elided
drops) is indeed deadlock-free. Features like mutexes are written using the
unsafe sublanguage because shared memory concurrency is useful. Because
potential deadlocks are opt-in rather than opt-out, it's not unreasonable to
imagine that future extensions to Rust (or some other language in the same
mold) could bridge this gap in a practical way.

~~~
rcxdude
This is a bit of a misunderstanding of what Rust's safety guarantees are and
what 'unsafe' actually means in Rust.

Rust safety guarantees are based around a few rules involving references,
ownership, lifetimes and borrowing. None of them forbid the creation of
mutexes or anything else which you will find in Rust.

'unsafe' Rust code shouldn't break these rules any more than the 'safe' code
does, the only difference is that the compiler lets you do something which it
doesn't know is safe. It's up to you to design the code such that it is.

If code marked as 'unsafe' does something which actually violates one of the
assumptions on which the Rust compiler works, then the safety of the whole
program is jeopardised.

(There was actually a bit of bike-shedding surrounding possibly renaming the
'unsafe' keyword to better reflect these facts, which included suggestions
like 'trustme' and 'yolo', but a satisfactory term was not found)

~~~
Jweb_Guru
You are correct. When I refer to the safe sublanguage, I mean the language
without any unsafe blocks. Since so much of Rust is defined in libraries,
precisely what is allowed in those unsafe blocks is a fairly important detail;
they can violate semantic guarantees that "pure" safe Rust provides. In this
case, the Rust core team made the deliberate choice (which they have discussed
on occasion) not to consider deadlocks unsafe behavior, because they were not
able to get it to work to their satisfaction. As another example, there are
fairly subtle implications for concurrency around the fact that Rust provides
(using unsafe code) a swap function for mutable references. Swap cannot be
written in the pure safe sublanguage. Yet another example: in pure, safe Rust,
memory leaks are not possible (except via premature abort). This is not true
of Rc, hence Rust does not guarantee freedom from memory leaks.

------
tatterdemalion
Rust's concurrency primitives are as powerful as its semantic concepts are
expressive - both stunningly so for a language that compiles to this level.

Yesterday I wrote a macro to build simple actors[1], without a care for
robustness (this was just a rough draft and I didn't expect it to even work so
quickly - amazingly it did!). Now I'm looking at how to fill it out and make
it generally useful and get this: I didn't even try to handle failure yet, but
it already responsibly joins the thread and deallocates its resources as soon
as the actor can no longer receive messages. Automatically! As a natural
consequence of Rust's memory model and type system. Despite the fact that Rust
knows nothing about concurrency at the language level.

[1]
[https://github.com/withoutboats/Chekhov](https://github.com/withoutboats/Chekhov)

------
unfamiliar
I'd just like to say that I found the tone of this article so much better than
all of the other rust guides I have looked at. It managed to be interesting
without being annoyingly cute or overly dry or explaining basic concepts which
any programmer will know. It makes me want to go back and have another look at
the language because it presents the ideas and upsides in such a pleasant
manner. Well done.

------
rntz
This post observes that

> the same tools that make Rust safe also help you tackle concurrency head-on.

The tools that make Rust safe, in this instance, being its ownership type
system. Why on earth should that be the case? Why would ownership types make
concurrency easier? The post gives plenty of in-depth answers to this
question, but to my mind it misses the bigger picture.

The big-picture reason why ownership types and concurrency match so well is
the Curry-Howard correspondence.

Ownership types are known as linear[1] types in the PL theory literature.
Linear types are so-called because they correspond to linear logic, a logic
sometimes described as "the logic of resources" rather than "a logic of
truth". In linear logic, instead of reasoning about eternal truths like "1 + 1
= 2", you reason about resources, like "I have an apple and a banana". If you
also happen to know that "from an apple, I can make applesauce", then linear
logic lets you reason that you can obtain the state "I have applesauce, and a
banana" (but no longer an apple).

However! Linear logic has another interpretation, a Curry-Howard
interpretation. You can also view it as a type system for a concurrent
language based on message-passing, where your types describe the _protocols_
message-channels obey. "I have an apple and a banana" corresponds to a channel
which will first produce an apple, then a banana, then close. This is known as
the session-types interpretation[2].

At first glance it may seem that this interpretation of linear logic and
Rust's interpretation are unconnected. I doubt it; in logic, everything
connects to everything else. It's no coincidence, for example, that ownership
(linearity) is what you need in order to send mutable values across a channel
without copying. There is a deeper structure waiting here to be discovered,
and I for one am deeply excited about it.

[1] In Rust's case, technically they are affine types. An affine type is a
linear type whose values can be freely dropped, i.e. deallocated.

[2] Session types have been around for a long time, but only fairly recently
was the connection to linear logic discovered.

Links/papers on session types:

1."Propositions as Sessions" by Wadler
([http://homepages.inf.ed.ac.uk/wadler/papers/propositions-
as-...](http://homepages.inf.ed.ac.uk/wadler/papers/propositions-as-
sessions/propositions-as-sessions-jfp.pdf))

2\. Caíres and Pfenning, "Session types as intuitionistic linear propositions"
([http://www.cs.cmu.edu/~fp/papers/concur10.pdf](http://www.cs.cmu.edu/~fp/papers/concur10.pdf))

3\. Pfenning's published works page
([http://www.cs.cmu.edu/~fp/publications.html](http://www.cs.cmu.edu/~fp/publications.html)).

~~~
steveklabnik
We may end up adding linear types, not just affine, to Rust as well:
[https://github.com/rust-lang/rfcs/issues/814](https://github.com/rust-
lang/rfcs/issues/814)

~~~
cwzwarich
From the RFC you linked:

"First, the `linear` attribute as described here does not create true `linear`
types: when unwinding past a `linear` type, the `linear` attribute will be
ignored, and a `Finalize` trait could be invoked. Supporting unwinding means
that Rust's linear types would in effect still be affine."

~~~
arielby
Rust always has unwinding, and the finalizer can just call `abort` to make
this as linear as you can.

------
divs1210
Can someone please write a Clojury lisp that compiles to Rust without
immutability (by default) and concurrency primitives, since thread safety is
guaranteed by the rust compiler?

I love Clojure, but this would be a completely different beast, a crazed
DRAGON probably.

PS: I _want_ a Dragon

~~~
e12e
Sounds like you want a Rust port of Shen[1]. AFAIK there are no real[2]
efforts to do that... yet.

[1] [http://shenlanguage.org](http://shenlanguage.org)

[2]
[https://github.com/michaelsbradleyjr/shen.rs](https://github.com/michaelsbradleyjr/shen.rs)

------
westoque
And here's me, happy building apps with Erlang/Elixir with their concurrency
model.

~~~
lukego
Immutability sure makes this stuff easy in Erlang.

EriPascal, an ancestor of Erlang, used runtime ownership for passing mutable
binaries around. Once you send the binary in a message it is a runtime error
to use it again afterwards.

------
mlitchard
I can't wait for a mature idris+mature rust. It will be a better world then.

~~~
badsock
It makes me sad that Idris is (explicitly) never going to be an industrial
language - so many nice things in it.

~~~
mlitchard
I think it will do just fine as an industrial language, when you have the
senior architect being the idris expert. His job will be to write the DSL for
the engineers to code in. Idris already has hooks into C and Java and who
knows what else by then. It won't work well if you expect to replace your java
brigade with an idris brigade. Why would you need one anyway? I wonder if I'm
being naive.

------
eslaught
I am slightly confused about why the MutexGuard type is required in the
locking example. Why can locking a lock not just return &mut? The API as
written does so eventually anyway, through access(). I guess the API as
written allows you to call access() multiple times, but I do not see how this
is a useful property if the mutex stays locked for the entire scope of the
MutexGuard anyway.

~~~
aturon
The key is that the MutexGuard is responsible for _unlocking_ (and it does so
automatically on destruction). That way, you're tying the scope for the &mut
reference to the scope in which the lock is actually held.

~~~
VanillaCafe
Similarly, I quite don't understand what in the type signatures precisely tie
MutexGuard to the result of access.

Like, in the "Disaster averted" example the mutex section, how is it detecting
that "error: `guard` does not live long enough"? I see that access returns a
borrowed mutable T. But, how does the compiler know that that's borrowed from
guard instead of a hypothetical second or third parameter or from some other
(global?) state altogether?

Edit: Or maybe if a function returns a borrow, then the borrow cannot outlive
the scope of the function?

~~~
aturon
What's going on here is a bit of shorthand. If you don't use the shorthand,
the signature looks like:

    
    
        fn access<'a, T: Send>(guard: &'a mut MutexGuard<T>) -> &'a mut T;
    

This says that the incoming borrow and the returned borrows have identical
scopes (officially called "lifetimes" in Rust; 'a here is a lifetime
variable.) It's a very typical pattern: if you return some borrowed data, it
generally comes from a borrow you were given, and the shorthand allows you to
leave this implicit in cases where it's clear.

 __EDIT FOR CLARITY __: in particular, you 're saying that if you take a
"sublease" from something you've already borrowed, that sublease is valid for
no longer than the original lease. So for example if you borrow a field out of
a borrowed structure, the access to the field can last no longer than you had
access to the original structure. In this case, we're saying that access to
the data lasts no longer than the lock is held.

I'd recommend [http://blog.skylight.io/rust-means-never-having-to-close-
a-s...](http://blog.skylight.io/rust-means-never-having-to-close-a-socket/) if
you want to get a bit more of a feel for how this works.

The details about the shorthand are here: [https://github.com/rust-
lang/rfcs/pull/141](https://github.com/rust-lang/rfcs/pull/141)

------
halayli
everything mentioned can be done in C++11 and lthread_cpp

[http://lthread-cpp.readthedocs.org](http://lthread-cpp.readthedocs.org)

~~~
Jweb_Guru
What is interesting is not so much that Rust has shared-memory concurrency
(BTW, isn't lthread a M:N threading library? Everything in the article is 1:1)
but that it can make this statically safe. Perhaps I am missing something, but
I see no evidence from your repository that lthread enforces any of the static
safety guarantees discussed in the article, which is not surprising as many of
them fundamentally depend on lifetimes and borrowing.

~~~
halayli
Possible M:N.. by default it's 1:1. It's 1 lthread scheduler per actual
pthread.

static safety guarantees can come with auto pointers (unique_ptr in
particular). This is why I said lthread_cpp & C++11.

~~~
Jweb_Guru
std::unique_ptr is a significant improvement over prior options, but it can
neither ensure correct usage of a mutex (in the sense that it can't cause a
data race) nor preserve data race safety for references into another thread's
stack. Both of these rely on Rust's ability to limit reference lifetimes,
which C++11 doesn't really have (C++14 has an extremely limited form of it in
rvalue references, but it's not sufficient to replicate what's in this
article). unique_ptr also can't really statically guarantee uniqueness (it's
done at runtime instead), meaning it doesn't quite work with channels; and in
combination with other C++ features (such as references or shared_ptr) you can
also see data races through it, since it doesn't prevent mutation; thus, it
isn't able to guarantee safety for use with channels either.

~~~
halayli
unique_ptr transfers ownership. No 2 threads can be working on the same object
unless you go out of our way to cheat unique_ptr behavior.

Statically or at runtime guarantees don't buy me much. Regarding mutations,
const and copy/move constructors give you want u want. Generally, all you need
to do is have 1 thread create an std::unique_ptr<Class> object and pass it to
the channel for other thread to pick it up.

~~~
Jweb_Guru
Regardless of whether you feel that these static guarantees buy you anything,
C++ does not have them and Rust does; the two solutions are not equivalent.
Const references only guarantee that you are not mutating through the
reference, not that someone else isn't--if you are writing a library that
accepts one, you cannot prevent misuse by users. This is an important
difference from Rust. In any case, you still haven't explained how unique_ptr
helps with the other two things I mentioned (ensuring that data protected by a
mutex cannot be accessed without acquring the lock, and safely sharing and/or
mutating data on another thread's stack). The blog post explains how Rust does
it; C++ simply does not have the necessary types.

