
How does the concurrency primitives in Rust compare to the ones in Go? - samuell
From what I read about the concurrency primitives in Rust, they seem to be more inspired by the actor model in languages such as Erlang (and Scala?) than Golangs &quot;hardwired&quot; channels.<p>Is this notion correct?<p>At the same time it seems it has the counterparts of go-routines (lightweight tasks) and I have seen channels mentioned.<p>All in all, I would welcome a little elaboration on what are the differences between the concurrency primitives in the two languages.
======
GeorgeMac
I cannot speak for Rust, but I can speak for Go. The actor model is almost the
reverse of how Go handles concurrency. Instead of sending to named actors, in
Golang you name your pipes (or channels). Then you handle these pipes as first
class citizens and routines can choose to receive or send on them. e.g.

1\. In your main routine you build a channel of ints (noticed they're typed)
named `x`

2\. You hand `x` and an integer i (incremented by one for each new routine) to
ten routines which will double that number and send the result down `x`

3\. Your main routine then receives on `x` and builds a list of results. It
hasn't blocked on the doubling operations for each integer `i` because you
sent each of those operations in a new routine.

Ignoring the fact I haven't managed closing the channel `x` you can get the
gist of how channels are named and passed around.

I can't say what the alternative would be in Rust, but I hope that sheds more
light on Go!

~~~
steveklabnik
This is similar to Rust:

    
    
        let (tx, rx): (Sender<int>, Receiver<int>) = channel();
        
        spawn(proc() {
            let result = some_expensive_computation();
            tx.send(result);
        });
    
        some_other_expensive_computation();
        let result = rx.recv();
    

There are some subtle details I'm not 100% sure of (bounded vs. unbounded?)
that may be different, though.

For more: [http://doc.rust-lang.org/guide-tasks.html](http://doc.rust-
lang.org/guide-tasks.html)

~~~
rakoo
This is completely the opposite: in Rust you have an explicit sender and an
explicit receiver, and you have no control on the channel that was created.
Only this sender can send to the pipe, and only this receiver can receive.

In go, all you have is the channel. Anyone can send to it/read from it:

    
    
        c := make(chan int)
    
        go func() {
            // I can write here
            c <- 1
        }()
    
        go func() {
             // I can also write here
             c <- 2
        }()
    
        // I can read here, even though I have no idea who wrote to the channel
        for a := <- c {
            // Note that since I don't know who wrote to c, I can't expect 
            // any order here. All I can do is process stuff that is coming
            // down the pipe (which is all I really care about after all
        }

~~~
dragonwriter
> This is completely the opposite: in Rust you have an explicit sender and an
> explicit receiver, and you have no control on the channel that was created.
> Only this sender can send to the pipe, and only this receiver can receive.

Its not "completely the opposite", though it is _different_. Rust doesn't let
multiple tasks use the _same_ sender handle, but supports multiple senders on
the same _channel_ by cloning the sender handle (same with the receiver
handle, _mutatis mutandis_.)

So your Go code becomes something like this in Rust (the only substantive
difference is the need to clone the sender handle):

    
    
      let (tx, rx) = channel();
    
      spawn(proc() { 
        tx.send(1); 
      });
      
      tx2 = tx.clone();
      spawn(proc() { 
        tx2.send(2); 
      });
      
      for a in rx.iter() {
        // As in the Go example, process the stuff coming down
        // the channel with no expected order or knowledge of
        // who sent to it. 
      }

------
tomp
You are correct.

(1) Rust disallows shared-memory concurrency (with a few exceptions), so each
"task" has it's local heap. Only unique objects can be transferred from one
task to another. This is similar to Erlang.

(2) They were using light-weight processes initially, but then transitioned to
a 1-on-1 threading model, where each task runs on its own thread. IIRC it was
to reduce the runtime library (lightweight threads require stack-growth
handling and a user-mode scheduler).

~~~
steveklabnik
Rust actually supports both 1:1 and M:N threading models, depending on which
one you want. It's true that it used to be M:N by default and is now 1:1 by
default, though.

> Rust disallows shared-memory concurrency (with a few exceptions),

To elaborate: Rust eliminates mutable state being passed across a concurrency
boundary at compile time. Sometimes, you really need shared mutable state,
though, so you're able to use 'unsafe' to implement the safety the compiler
can't infer for you. The obvious ones (only allowing access via a mutex and
reference counting (both atomic and non)) are already in the standard library.

~~~
samuell
That's interesting with the M:N threading model. But would that require that I
set the M/N ratio (or "multiplexing factor" or what to call it) to a fixed
value, unlike the ability in Go to automatically multiplex any number of go-
routines on the (fixed) number of OS threads?

~~~
steveklabnik
I'm not intimately familiar with Go's details here, but you can either just
say 'run these green threads kthx' in which that factor is chosen for you, or
you can use an explicit pool(s) and do it yourself.

Details: [http://doc.rust-lang.org/green/index.html](http://doc.rust-
lang.org/green/index.html)

------
cjslep
Go's core philosophy for concurrency is: _Do not communicate by sharing
memory; instead, share memory by communicating_ [1]. I have not tried to share
memory (ex: a pointer-to-struct) between two different go threads before, so I
can't really comment on what happens when one tries to break out of the
language's philosophy. However, I've found that following the philosophy is
incredibly liberating as it encourages a certain kind of reasoning that gives
me the confidence I can accurately statically analyze the code without having
to debug it at run time. Like any other philosophy though, it isn't for
everyone.

EDIT: I now realize I never addressed your concern of primitives. Go really
only has one: the channel. It can send anything (even other channels). The two
types are buffered or unbuffered, and deciding which to use can alter the
behavior of a program.

There are a few language keywords to support the use of channels, such as
`for-select-case` loops and `go` to spawn new goroutines. The former is used
to help respond to numerous channels, the latter to spawn more goroutines.
Nothing in the language prevents listening to a runtime-determined arbitrary
number of channels, either.

[1] [http://blog.golang.org/share-memory-by-
communicating](http://blog.golang.org/share-memory-by-communicating)

~~~
mratzloff
> _I have not tried to share memory (ex: a pointer-to-struct) between two
> different go threads before, so I can 't really comment on what happens when
> one tries to break out of the language's philosophy._

As in other languages, you have to handle it--either through locking or some
other means.

------
dragonwriter
Rust channels seem more like Go's channels than Erlang's actor mailboxes -- in
Rust like Go and unlike Erlang, a task can receive from multiple channels, not
just the one that it owns as a result of being an actor.

The difference is that while channels support multiplexing at both ends in
Rust, just as they do in Go, that multiplexing requires explicit cloning of
the handles in Rust, whereas it is the default behavior in Go. So, Rust
channels are like Go channels but where multiplexing is explicit rather than
implicit.

