
Concurrency in Rust - SirNoobsAlot
http://doc.rust-lang.org/book/concurrency.html
======
gregwebs
Send + Sync are great. The downside of concurrency in Rust is:

1) There isn't transparent integration with IO in the runtime as in Go or
Haskell. Rust probably won't ever do this because although such a model scales
well in general, it does create overhead and a runtime.

2) OS threads are difficult to work with compared to a nice M:N threading
abstraction (which again are the default in Go or Haskell). OS threads leads
to lowest common denominator APIs (there is no way to kill a thread in Rust)
and some difficulty in reasoning about performance implications. I am
attempting to solve this aspect by using the mioco library, although due to
point #1 IO is going to be a little awkward.

~~~
IshKebab
There's no way to kill goroutines either. In fact, are there any systems that
allow you to cleanly kill threads?

~~~
rdtsc
Yes.

In Erlang:

    
    
        exit(kill).
    

or exit(Pid,kill).

Will kill a process. It has an isolated heap, so it won't affect other
(possibly hundreds of thousands of) running processes. That memory will be
garbage collected, safely and efficiently.

This will also work in Elixir, LFE and other languages running on the BEAM VM
platform.

EDIT: masklinn user below pointed out correctly, the example is exit/2, that
is exit(Pid,kill). In fact it is just exit(Pid, Reason), where Reason can be
other exit reason, like say my_socket_failed. However in that case the process
could catch it and handle that signal instead of being un-conditionally
killed.

~~~
bluejekyll
This is called from within a threads execution, right? I think the question is
about being able to kill a thread externally.

Java had this in 1.0 or 1.1 and then thought better of it and deprecated the
API.

~~~
rdtsc
It works correctly either way -- externally with exit(Pid,kill) or by the
process itself as exit(kill). The last one is just a shorthand for
exit(self(), kill). Where self() is the process id of the currently running
process.

~~~
masklinn
> It works correctly either way

But the way you showed was not one in which anyone was interested, synchronous
exceptions work in more or less every language, and you can't assume readers
know your self-killing is actually implemented via asynchronous exceptions
since they don't know the language.

------
Manishearth
For a more in-depth explanation of how Send and Sync work theoretically, see
[http://manishearth.github.io/blog/2015/05/30/how-rust-
achiev...](http://manishearth.github.io/blog/2015/05/30/how-rust-achieves-
thread-safety/) or [http://huonw.github.io/blog/2015/02/some-notes-on-send-
and-s...](http://huonw.github.io/blog/2015/02/some-notes-on-send-and-sync/)

------
nindalf
I think Steve Klabnik could clarify this, but the book at that link is in the
process of being rewritten. I think it might be good to wait until it is. I
personally found it slightly difficult to follow compared to other options
like the soon to be published Programming Rust.

~~~
steveklabnik
I am in the middle of working on a second draft of the book. This page is one
of the oldest bits of docs, overall, and isn't my best work. It's not _wrong_,
I just have very high personal standards. It was adapted from older
documentation and was written in the time up to 1.0, where I had a LOT on my
plate.

~~~
galonk
It certainly is _confusing_ if not _wrong_. You silently add "move" to the
closure without any mention or explanation (then later say "note that we're
copying i" without explaining that you're talking about the "move" keyword).

The bit about Mutex also has a "just type this to fix the problem with no
explanation of how or why it works" flavour (although I guess if you already
grok mutexes then its use here might be obvious to you).

Not a criticism of the tutorial, but it does something common in Rust
tutorials which is really a problem with the language at this point: Rust
tutorials always spend a lot of time interpreting Rust's notoriously poor
error messages (e.g. "what this message that doesn't mention Sync is trying to
tell you is that you need Sync on this type"). That's great when you're doing
the tutorial, but as soon as you're on your own man are those errors
frustrating.

~~~
Manishearth
> Rust's notoriously poor error messages

I've actually heard the opposite feedback; Rust's error messages try to be
super helpful.

Note that concepts like ownership and Sync are new to most programmers, and
it's impossible to explain them in an error message. This is where the
extended error messages (via --explain) and the book come in.

The tutorial has this style because Rust espouses catching things at compile
time, so the tutorial _demonstrates_ this being done by doing the wrong thing
a few times and reiterating why it gave an error.

\----

Could you give examples of confusing error messages? I'd love to improve them.
The one you mention .... doesn't exist. This
([http://is.gd/keukPm](http://is.gd/keukPm)) is what that error message looks
like, and (a) it mentions Sync, (b) it also mentions that `Arc<bool>` cannot
be shared between threads safely.

If you were referring to "So, we need some type that lets us have more than
one reference to a value and that we can share between threads, that is it
must implement Sync." from the book, the last part about Sync has nothing to
do with that error message. If you follow the book, the error message is clear
without the context of threads -- `data` was moved into the first spawn() call
and the subsequent ones can't use it. One does not conclude that Sync is
necessary from this error message, and that's not what the book is trying to
say.

This sentence is actually skipping a step, one like
[http://is.gd/RPNOm4](http://is.gd/RPNOm4), where the compiler asks you for a
Send type (or a Sync type, depending on the exact code). Instead of stepping
through this example, it just introduces Sync directly by noting that we're
dealing with threads anyway and the reader already knows what Sync/Send are.
It doesn't _conclude_ that Sync is necessary from the error message.

> The bit about Mutex also has a "just type this to fix the problem with no
> explanation of how or why it works" flavour (although I guess if you already
> grok mutexes then its use here might be obvious to you).

The previous sentence says "for example a type that can ensure only one thread
at a time is able to mutate the value inside it at any one time.", which is
exactly what a mutex does. Unless you want a lower level explanation which IMO
isn't necessary. It could explain locking more though; I'll fix that.

~~~
firebones
I've found the Rust compiler to provide very useful messages. That said, it
does take a certain amount of experience in the language before you grok the
terminology and appropriate remedies to overcome some compiler errors. As a
beginner, I'd typically open about 5-10 different documentation, blog post, SO
and tutorial pages to try to figure out what I was doing wrong. Once I
understood the underlying concept, I could go back and understand what the
compiler message was telling me, as plain as day. After awhile, I had a strong
enough understanding of the borrow checker that I could usually interpret the
compiler error messages in a pretty straightforward fashion. But it takes time
if you're coming from a non C/C++ language!

If you're learning Rust, I would recommend tracing back every compiler error
you encounter to this page [1] to see some other examples and resolutions (if
the compiler's suggestions don't make it clear as to what to do). It's the
most underrated page in the entire Rust documentation set, and could be the
launch point for a lot of teaching and learning. Read the book to start,
certainly (and the revisions Steve Klabnik has been making are very, very
good), but that error index page is very valuable for the day to day issues.

Rust rewards gumption [2] and perseverance.

[1] [https://doc.rust-lang.org/error-index.html](https://doc.rust-
lang.org/error-index.html) [2]
[https://en.wikipedia.org/wiki/Gumption_trap](https://en.wikipedia.org/wiki/Gumption_trap)

~~~
Manishearth
> you encounter to this page [1]

I love the extended errors. Note that you can just use `rustc --explain <error
code>` without opening a web page, but of course it won't be formatted then.

> But it takes time if you're coming from a non C/C++ language!

Ultimately there's little substitute to learning the concepts behind the
language :) Error-driven-development is fun, and quite useful once you know
the language, but if used as the only way to learn the language it can be
problematic. That said, I enjoy using errors to explain Rust concepts -- as
long as you _explain_ them. If a new user is hit with a steaming pile of
errors you can't really expect them to understand it directly.

I have talked with a lot of folks coming from a non-systemsy background, and
sort of have an idea of the things that get confusing (I once had similar
problems when learning C++). I'm planning a blog post series (no ETA, pretty
swamped with other work :/ ) that teaches both Rust (specifically, the safety
bits like the borrow checker) and what's going on under the hood (regarding
memory, the stack/heap, etc) using each as a crutch to explain the other in a
leapfrog way, to introduce the language and low-level programming to folks
coming from languages like JS.

------
jonreem
Another thing to know about rust concurrency is that it supports safe "scoped"
threads, or threads which have plain references to their parent threads stack.

This makes it very easy to write, for instance, a concurrent in-place
quicksort (this example uses the scoped-pool crate, which provides a thread
pool supporting scoped threads):

    
    
        extern crate scoped_pool; // scoped threads
        extern crate itertools; // generic in-place partition
        extern crate rand; // for choosing a random pivot
    
        use rand::Rng;
        use scoped_pool::{Pool, Scope};
    
        pub fn quicksort<T: Send + Sync + Ord>(pool: &Pool, data: &mut [T]) {
            pool.scoped(move |scoped| do_quicksort(scoped, data))
        }
    
        fn do_quicksort<'a, T: Send + Sync + Ord>(scope: &Scope<'a>, data: &'a mut [T]) {
            scope.recurse(move |scope| {
                if data.len() > 1 {
                    // Choose a random pivot.
                    let mut rng = rand::thread_rng();
                    let len = data.len();
                    let pivot_index = rng.gen_range(0, len); // Choose a random pivot
    
                    // Swap the pivot to the end.
                    data.swap(pivot_index, len - 1);
    
                    let split = {
                        // Retrieve the pivot.
                        let mut iter = data.into_iter();
                        let pivot = iter.next_back().unwrap();
    
                        // In-place partition the array.
                        itertools::partition(iter, |val| &*val <= &pivot)
                    };
    
                    // Swap the pivot back in at the split point by putting
                    // the element currently there are at the end of the slice.
                    data.swap(split, len - 1);
    
                    // Sort both halves (in-place!).
                    let (left, right) = data.split_at_mut(split);
                    do_quicksort(scope, left);
                    do_quicksort(scope, &mut right[1..]);
                }
            })
        }
    

In this example, quicksort will block until the array is fully sorted, then
return.

------
pmarreck
Reading all this is making me happy about pursuing Elixir (which is of course
a language addressing largely different use-cases)

------
djhworld
I'm having a tough time trying to understand this snippet

    
    
        for i in 0..3 {
            thread::spawn(move || {
                data[i] += 1;
            });
        }
    

What is the 'move' thing here before the ||

~~~
barosl
So, for those who may know JavaScript, you may have seen code like this:

    
    
      var closures = [];
      for (var i=0;i<5;i++) {
          closures.push(function() {
              console.log(i);
          }); 
      }
    

The code above will result in incorrect results: 5, 5, 5, 5, 5. Because you're
capturing `i` as a reference.

To avoid this, JS devs typically do this:

    
    
      closures.push((function(i) {
          return function() {
              console.log(i);
          };
      })(i));
    

Or, if you can afford the ES6 support:

    
    
      for (let i=0;i<5;i++) {
          /* `let`-scoped `i`s are created individually at each iteration, so it is safe to capture them by references. */
      } 
    

Rust supports this pattern by a built-in syntax. That's what `move` means. If
you prepend the `move` keyword before the closure, the variables will be
captured by values, not references.

~~~
kentosi
Thanks for the explanation, but how would the move keyword know that you're
referring to the i variable? Ie - what is there were multiple variables (such
as a for loop with j in there)?

Wouldn't it have been a better approach to add some sort of demarkation, such
as i* or i^ (or whatever) to indicate this?

Just curious.

~~~
masklinn
> Wouldn't it have been a better approach to add some sort of demarkation,
> such as i* or i^ (or whatever) to indicate this?

That's the path C++ took[0], the Rust people thought it had too much syntactic
and semantic overhead, and that having just "move" and "referring" closures
would be much simpler. If you want to mix them up, it's easy enough to create
references outside the closure (and capture them by value with a move closure)

[0]
[http://en.cppreference.com/w/cpp/language/lambda#Lambda_capt...](http://en.cppreference.com/w/cpp/language/lambda#Lambda_capture)

~~~
steveklabnik
There was a really good thread on this on /r/urust:
[https://www.reddit.com/r/rust/comments/46w4g4/what_is_rusts_...](https://www.reddit.com/r/rust/comments/46w4g4/what_is_rusts_lambda_syntax_and_design_rationale/)

In particular, don't miss this post:
[https://www.reddit.com/r/rust/comments/46w4g4/what_is_rusts_...](https://www.reddit.com/r/rust/comments/46w4g4/what_is_rusts_lambda_syntax_and_design_rationale/d08hee0)

------
z1mm32m4n
Does Rust have a way to work with SIMD concurrency as opposed to just
fork/join concurrency? Something along the lines of how openmp or cilk let you
do a parallel for all?

~~~
fndjdh
You seem to have concurrency confused with parallelism. Concurrency just means
working on another task before the prior one has completed. You're describing
parallelism which is doing multiple tasks at the same time.

~~~
m0th87
Parallelism necessarily implies concurrency [1]

So, "SIMD concurrency" is not incorrect (although SIMD parallelism is more
correct :)

1:
[http://programmers.stackexchange.com/a/155110](http://programmers.stackexchange.com/a/155110)

~~~
naasking
This not true. CPUs have instruction parallelism, even on a single CPU, but
there is no observable concurrency.

SIMD is data-level parallelism, not concurrency.

------
armitron
This looks terribly overcomplicated/overengineered to me, to the point where I
doubt many are going to adopt/switch to this style, esp when used to more
convenient approaches [even the standard C++ approach, faulty as it may be].

Also note how much boilerplate one has to write and how the code snippets
bypass error handling (do it differently in "real" code but don't show us
how). Bleh.

~~~
bluejekyll
Try it before you mock it.

This prevents boiler plate issues, and allows the compiler to help you
discover threading issues at compile time rather than runtime.

It's easy enough to just mark all you structs send+sync and still shoot your
foot off just like in any language. The point is, you need to be explicit that
your trying to shoot your foot off, as opposed to other languages which
basically pull the trigger for you.

------
RasmusWL
Can someone enlighten me as to why the first snippet has a data race? Won't
the resulting array become [2,3,4]?

    
    
        let mut data = vec![1, 2, 3];
    
        for i in 0..3 {
            thread::spawn(move || {
                data[i] += 1;
            });
        }

~~~
Manishearth
That's a mistake, clarified: [https://github.com/rust-
lang/rust/pull/32538](https://github.com/rust-lang/rust/pull/32538)

------
askyourmother
What about the assumption (fatally flawed decision?) that malloc never fails
when rust asks? Sounds like something that could affect concurrency

~~~
Manishearth
A failed malloc aborts the process. If this is important to you, don't use the
heap abstractions in the stdlib then. This is no different from the situation
in C++.

(You can also plug in a custom allocator which behaves differently)

~~~
qznc
On Linux malloc never fails actually. Instead, the kernel kills processes if
it runs out of memory.

~~~
Manishearth
A lot of folks are pointing out that malloc can fail, which is true, but the
important part is that there are situations where your application will just
abort randomly in the middle of nowhere (i.e. not during malloc) and there's
nothing you can do about it. There are _also_ situations where malloc fails
and returns a null, but given the existence of situations with no errors on a
malloc, handling the error in these cases isn't close to a solution. No
language or stdlib can solve this problem 100%.

At a baremetal level, it helps having this, but you're probably better off not
using the stdlib anyway.

~~~
quotemstr
Bullshit. Every part of the C++ standard library (which is _much_ bigger than
Rust's) can gracefully propagate resource exhaustion errors, including memory
allocation failure, to callers. That Rust can't is a design flaw.

> there are situations where your application will just abort randomly

That's sure as hell not the case in any environment I choose to use.

~~~
Manishearth
> can gracefully propagate resource exhaustion errors, including memory
> allocation failure, to callers

only on malloc. If the kernel overcommits, your process will abort when you
try to _use_ the memory, possibly way after the malloc and there's nothing you
can do about it. That's the point being made here.

> That Rust can't is a design flaw.

(This is false, see Steve's reply above about this)

~~~
quotemstr
The world is not Linux. I happen to believe that believe that overcommit in
the Linux kernel is a disgrace. It is, however, at least possible to disable
it. It's not possible to retroactively add real exceptions to Rust, or to
change the signature of all memory-allocation function to return Result.

Rust is supposed to be a general-purpose systems programming language, not a
Linux programming language. Windows does not overcommit. A correctly
configured Linux system does not overcommit. Lost of embedded systems don't
(and can't) overcommit. Are you saying all of these people should avoid Rust's
standard library?

> > That Rust can't is a design flaw.

> (This is false, see Steve's reply above about this)

It's clear that my opinion differs from that of many Rust developers and
users. I still think I'm correct, that these developers and users are
misguided, and that as Rust attempts to fill more niches, experience will show
that my position is the correct one.

All I can say is that I personally will not use any language that bakes
cornucopian assumptions about memory baked into its core library. I know that
you say that it's possible to just avoid stdlib --- but the temptation to use
it will be irresistible, and once somebody succumbs to the temptation, the
entire program is now capable of aborting irrecoverably.

I will stick with languages . Modern C++ is safe and expressive enough, and it
correctly reacts to resource exhaustion.

~~~
Manishearth
> The world is not Linux.

Sure, but if linux has this issue, then C++ programs on linux will also have
this issue, and the language can't solve that. That's all my point was.

> or to change the signature of all memory-allocation function to return
> Result.

When custom allocators part 2 happens, you can. I've already argued the "real
exceptions" part above.

> Rust is supposed to be a general-purpose systems programming language, not a
> Linux programming language. Windows does not overcommit. A correctly
> configured Linux system does not overcommit. Lost of embedded systems don't
> (and can't) overcommit. Are you saying all of these people should avoid
> Rust's standard library?

No. My point was simply that no language has a _complete_ solution to this
problem.

Most people don't need to worry about OOM; abort-on-OOM is the expected
behavior. For the people who do, there is a mechanism to handle it, as
explained above. I can't help it if you have an idealogical issue with that
mechanism. But ultimately, it works and can be used.

~~~
quotemstr
> Sure, but if linux has this issue, then C++ programs on linux will also have
> this issue

Why even bring Linux into the discussion? A Rust program running on Windows
has the same problem.

> No. My point was simply that no language has a complete solution to this
> problem.

A correct C++ program running on Windows will not spuriously abort. Neither
will a C++ program running on a Linux system configured not to overcommit.
That some Linux systems can be configured to kill processes at arbitrary times
is not an excuse for Rust to be sloppy with memory allocation.

C++ and many other languages _do_ , in fact, have _complete_ solutions to this
issue, and that Rust does not is a serious deficiency, one serious enough to
prompt me to prefer other languages despite Rust's other advantages.

~~~
Rusky
...and the same is true of Rust, just with different defaults. Switch
allocation failure from an abort to a panic and catch the panic just like you
would in C++.

What's incomplete about that? It's not like C++ can't be switched in the other
direction with -fno-exceptions or equivalent.

~~~
quotemstr
-fno-exceptions is not part of C++. C++ is not whatever GCC and Clang happen to accept.

