Writing nontrivial multithreaded code using thread and mutex primitives is very hard to get right (not only due to data races, but also race conditions and deadlocks). Has your experience been different?
>Show me a language that statically prevents deadlocks
OK, idiomatic Go and Erlang will never deadlock. Sure, you can use mutexes in Go, but unlike in Rust they aren't the only means of achieving high levels of concurrency (in fact, they are explicitly discouraged).
I find this attitude from the Rust community disheartening. Not everything needs to be statically verified to be useful.
https://talks.golang.org/2012/concurrency.slide#46
This is great for a couple reasons:
It's automatically concurrent without explicit pooling and locking.
The code flow remains sequential. No callback hell!
It reduces concurrency errors. The locking pattern is naturally acyclic! Nothing races!
Ah, but Go has a big runtime, you say! We can't have that in Rust! Well, here's the same thing in C and in C++:
http://libmill.org/tutorial.html
http://www.boost.org/doc/libs/1_59_0/libs/coroutine2/doc/htm...
Coroutines are a wonderful tool for building concurrent applications, and I dearly hope we get them in Rust.
Forget to unlock a mutex when dealing with shared data? Deadlock (and Go doesn't have RAII mutexes, so it's very easy to do this).
The pattern you put forth is possible in Rust too. Use mio if you want goroutine-like efficiency. Nothing new.
To be fair, how long would that take to debug?
>Forget to unlock a mutex when dealing with shared data?
You'll note there are no mutexes. RAII mutexes are great (although less useful without exceptions). But the entire point of Go's concurrency model ("share by communicating") is that you don't need need to deal with them.
>Use mio if you want goroutine-like efficiency
For a personal project, I would. But will any commercial entity use an unstable, unportable library with a single maintainer for critical functionality in their app? Because that's the concurrent IO situation in Rust, now and for the foreseeable future.
> But the entire point of Go's concurrency model ("share by communicating") is that you don't need need to deal with them.
Exactly the same thing works in Rust, and works "better": the lack of other sharing (except by message passing) is enforced at compile time. Rust ensures that other options are available with as much help for correctness as possible.
> But will any commercial entity use an unstable, unportable library with a single maintainer for critical functionality in their app?
Concurrent IO is inherently unportable, and mio has support for the major platforms (OSX, Linux and Windows, with tests run on all) so I don't know what you mean by that. mio won't be the first or last lib with a single maintainer that a commercial entity uses.
(Unstability is of course a perfectly reasonable criticism, and I'm sure it'll disappear as the library ages.)
Write the version with explicit locks and condition variables and we'll see if it's as easy to debug. :P
>Exactly the same thing works in Rust, and works "better": the lack of other sharing (except by message passing) is enforced at compile time.
Which is one of the reasons why Rust can (should) eat Go's lunch. All it's missing are lightweight coroutines and concurrent IO.
And I didn't know Mio supports Windows now — that's good news! If Mio stabilizes and Rust gets lightweight coroutines (probably requiring compiler support), Rust could be the best of all worlds.
In any case, you seem to be ignoring what everyone is saying: Rust doesn't guarantee deadlock freedom, but it still tries to help. Mutexes can be an important building for some things, but they're not the final story. There's atomics and channels in the standard library right now, and now that 1.0 is released, there'll be a growth of even better abstractions.
One of the people working full time on Rust has a PhD in concurrent programming, and has it as a personal goal to make Rust great at it. You can see his initial work on his blog[1], and read his thesis which introduces "reagents" (something he has expressed interest in implementing in Rust)[2].
1.0 is the start for Rust, not the end.
https://en.wikipedia.org/wiki/Deadlock
Either way, redefining the word does nothing to make mutexes and native threads safer or easier to use in practice.
I can't emphasize this enough — data races are not the only kind of concurrent programming error, yet they are the only kind prevented by borrow checking. Go, Erlang, and Node have high level approaches to concurrency that reduce other categories of errors (in addition to providing massive performance benefits vs. naive native threading).
I think the time to add good concurrency abstractions to a language is before 1.0, but I'm in strong disagreement with the community there. And judging by this thread, concurrent IO isn't even on the core team's roadmap. This is not a good sign for a language that sells itself on concurrency!
Look at the situation 10 months ago. How much has it improved?
https://www.reddit.com/r/rust/comments/2l0a4b/do_rust_web_se...
Channels are a step in the right direction, but are pretty limited without coroutines (channels without coroutines are just synchronized queues). Atomics are cool, but they address a totally different problem. Reagents, well, let's see an implementation.
C++ has needed a successor for many years now, and Rust is the best candidate. But lofty claims notwithstanding, the Rust concurrency situation is pretty dreadful.
And focusing on hangs in concurrent programs that are caused by using a data structure called "mutex" doesn't stop one getting exactly the same symptoms via your apparently-perfect channels.
One can use channels to get all four of the conditions required for a deadlock, especially Go's synchronous-by-default channels.
Any time you have a protocol of multiple tasks communicating with each other in some structured way, it's possible to break that protocol and hence have tasks sitting around waiting for messages that aren't coming. Especially in languages like Go/JavaScript/... which aren't powerful enough to model things like session types in their type system, e.g.
https://www.reddit.com/r/rust/comments/3jhd56/session_types_...
> I can't emphasize this enough — data races are not the only kind of concurrent programming error, yet they are the only kind prevented by borrow checking
Yes, that's exactly why the whole Rust community tries to be very careful about using "data race" when talking about concurrency in Rust.
However, it is the case that data races are the worst sort of concurrency bug: they are undefined behaviour and so can lead to arbitrary memory corruption, possibly only appearing a long way from the actual place with UB. Deadlocks and other problems are, by default, much more controlled in their failure modes.
Rust focuses on truly outlawing large classes of horrible problems: dangling pointers, iterator invalidation, data races (and all without requiring a garbage collector, although a GC barely helps with the latter two). It also tries to help with other problems with fewer guarantees, but even just being memory safe is a huge step up from widely used low-level languages (i.e. C/C++).
All the languages you mention are quite opinionated in their concurrency, imposing costs that Rust doesn't and can't, for its target space. (And, Go certainly doesn't provide any guarantees at all, not even data race freedom.)
> in addition to providing massive performance benefits vs. naive native threading
Important qualification: for IO bound tasks. Which is perfectly fine, but it needs to be understood.
> I think the time to add good concurrency abstractions to a language is before 1.0, but I'm in strong disagreement with the community there
What's so important about being pre-1.0? You seem focused on it, but I don't understand why. What benefit does Rust gain by delaying the release of 1.0 for months/years just to get good async IO support? Why is this particular pet feature any more important than everyone else's pet feature? (There have been so many requests: "why couldn't X make it into 1.0?")
If you're concerned about theoretical fragmentation of the ecosystem... that's not a problem in practice: mio is the standard.
> And judging by this thread, concurrent IO isn't even on the core team's roadmap
Wrong, it's very much on the roadmap, e.g. Alex Crichton (core team member) has been adding windows support to mio himself.
> Look at the situation 10 months ago. How much has it improved?
A lot. There's a burgeoning ecosystem built around mio.
---
In any case, Rust has been stable for barely 3 months. Be patient, and give it time for the concurrency story to blossom from the seeds that have been sown so far. Based on the experience so far, I'm pretty confident that Rust can easily be much better (i.e. more performant and reliable) than both Node and Go and even Erlang. (Of course, it may be syntactically less nice, since those languages bake it in deeply, while Rust is less opinionated.)
I've never used Go, but it has some interesting concurrency ideas. So do Node and Erlang. I naively expect Rust to adopt the best ideas from each.
>One can use channels to get all four of the conditions required for a deadlock, especially Go's synchronous-by-default channels.
One can, yes. But it's pretty easy to avoid cyclic locking patterns when each request is handled by a separate goroutine, as is idiomatic. One thread per request in Rust will drag pretty quickly.
Yes, the borrow checker is an impressive achievement. But is it enough for Rust to succeed? Marketing yourself as safer C++ is what Java already did (with tremendous success) 20 years ago. And the market for systems languages has only shrunk since then (my phone runs Java).
>All the languages you mention are quite opinionated in their concurrency, imposing costs that Rust doesn't and can't, for its target space.
And yet Rust has already partially standardized channels. Finish the channels, add coroutines and you've implemented Go! (I'll note there are already coroutine implementations for C and C++, which do not limit their use as systems languages).
>(And, Go certainly doesn't provide any guarantees at all, not even data race freedom.)
Which, interestingly, hasn't hindered its ability to become a successful language! A lesson worth remembering.
>Wrong, it's very much on the roadmap, e.g. Alex Crichton (core team member) has been adding windows support to mio himself.
I mean... https://github.com/carllerche/mio/graphs/contributors?from=2...
Could be worse, could be better?
>Why is this particular pet feature any more important than everyone else's pet feature?
I don't think "good concurrency is a pet feature" is the winning argument here.
>I'm pretty confident that Rust can easily be much better (i.e. more performant and reliable) than both Node and Go and even Erlang.
Me too! But I'm not sure that's going to happen with a single core developer on Mio and no timeline for standardization.
> I've never used Go, but it has some interesting
> concurrency ideas. So do Node and Erlang. I naively
> expect Rust to adopt the best ideas from each.
This is where your naivete shows. Rust originally did have the same thread model as Go baked into the language and standard library, and it labored for years to find a usable compromise between Go's green thread model and the native threading model. And a compromise is indeed necessary, firstly because we don't just need another Go, and secondly because Go's threading model imposes horrific costs when trying to interoperate with non-Go code (literally thousands of times the overhead that you'd expect). For a language like Rust that intends to interoperate with the native ecosystem, that overhead is unacceptable. After about three or four complete redesigns and rewrites the entire green threading infrastructure was chucked to the curb. Fortunately, Rust is low-level enough that libraries like mio can pick up the slack on their own, and in the meantime libraries that don't need green threads don't have to pay the price.Ehhhhhh not quite. Go provides one threading API, and it's green threading. Rust tried to provide the both green threading and native threading using identical APIs. That was a unique and in retrospect quixotic decision. Most of the problems identified in the RFC stem from the unified API issue:
https://github.com/rust-lang/rfcs/blob/0806be4f282144cfcd55b...
I also understand Rust's implementation was backed by libuv, which, being designed for Node, was a poor fit for Rust:
https://plus.google.com/+nialldouglas/posts/AXFJRSM8u2t
You're the third person in this thread to tell me that Go-like concurrency requires a big runtime, and it remains false. Here are analogous concurrency implementations in C and C++:
http://libmill.org/tutorial.html
http://www.boost.org/doc/libs/1_59_0/libs/coroutine2/doc/htm...
And because of that fallacy, the future of concurrency in Rust is a one-man show, third party library. It's a tremendous loss.
(Concurrency in C and C++ is also third-party libraries... The whole point of languages like Rust and C++ is that powerful functionality like this can be built externally, so that different trade-offs can be made. Languages like Go and Node force one approach, and so when you need something outside it, you're forced to do something suboptimal.)
Without the documentation, stability, portability, quality guarantees, and compiler support (that's a big one — code generation for coroutines needs to be good) of a standard library.
>Concurrency in C and C++ is also third-party libraries
C++ is on track to standardize concurrent file and network IO. Draft specifications have already been published, and Microsoft shipped coroutines in VS 2015. It would be a damn shame if C++ got concurrent IO before Rust.
I would like to use Rust professionally, and I'm sure you do/would as well. But no one can possibly sell their boss on using a project with a single part time maintainer to provide critical functionality.
That said, I do think a much better style of coroutines for Rust would be a C#-esque async/await transformation, converting stack-frames/local variables into an enum, allowing literally zero-cost coroutines (all the state is stored inline, no need to allocate a separate stack). Relevant issues:
- https://github.com/rust-lang/rfcs/issues/388
- https://github.com/rust-lang/rfcs/issues/1081
I'm pretty sure this is quite non-trivial to implement automatically.
---
C++ has had 20 years of stability, Rust only 3 months. Rust will get concurrent IO before C++ has on that time scale.
The goal with 1.0 was to stabilise enough of the language that people can start using it to write libraries that work into the forseeable future, allowing them to seriously explore the space of, for example, concurrent IO in Rust. Once enough exploration has been done (maybe you think enough has been done for async IO now), the functionality can start to become more official.
You can open VS 2015 today and use C++ coroutines backed by Microsoft (and their compiler, which is developed alongside their standard library).
And I am by no means saying that Goroutines are the final story in concurrent IO. Stackless coroutines in Rust would be a dream.
>C++ has had 20 years of stability, Rust only 3 months. Rust will get concurrent IO before C++ has on that time scale.
Concurrent IO is a hell of lot more important than it was in the 1990s, and the relative timescale is irrelevant for people choosing between Rust and C++ today (or Go, Scala, Clojure, C#, etc.).
>Once enough exploration has been done (maybe you think enough has been done for async IO now),
Exactly the opposite — I think the number of developers working on this (the Mio author plus Alex Crichton, maybe some offshoots) is far too few.
And the attitude I'm seeing from some core developers in this thread (concurrent IO is a "pet" feature that the community will someday deliver fully formed and ready for "blessing") is a huge disappointment.
There are cross-cutting concerns that apply to everything (including concurrent IO libraries) that development work is focusing on. Rust doesn't need to eat Go's/Scala's/Clojure's... lunch 3 months after it was released, taking a year or two to settle in and branch out seems fine to me.
Well, what domains is Rust targeting? Rust still isn't a good fit for embedded/kernel stuff without allocators and OOM handling (not to mention every architecture not targeted by LLVM).
That leaves userland applications, and how many applications don't need concurrent IO?
I'm definitely being impatient, and I'm sorry for that. It's just frustrating to see only one core member working on it.
Many user-space applications don't do heavy network work, which is where concurrent IO is most necessary. E.g. games or a scientific simulation, even web-browsers don't need of concurrent IO (they're not trying to juggle thousands of connections).
Which doesn't, correct me if I'm wrong, provide a stable allocator API or OOM handling yet.
>E.g. games
When your client pings 400 servers, how is it doing that? How is the game server implemented?
>a scientific simulation
That runs on one machine, and doesn't do a lot of disk IO? (concurrent file IO matters too).
>even web-browsers
Open the Chrome dev tools "network" tab, and then open Gmail. How many requests did it make?
Again, concurrent IO is important.