Live data from Hacker News

Fearless concurrency with Rust

blog.rust-lang.org

21–30 of 186 posts

Re: Fearless concurrency with Rust

#21

Earlier quoted context omitted.

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

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."

Oh interesting, I'd missed that. Thanks!

Re: Fearless concurrency with Rust

#22
post #17

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 possibl…

> 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...

Deadlocks can be statically prevented by ensuring that all code paths acquire locks in the same order. However, Rust's type system is not capable of enforcing that by itself (except maybe in some very limited cases). Some other substructural type systems (e.g. http://en.wikipedia.org/wiki/Substructural_type_system#Order...) might be up to the task, but I'm far from an expert in this area.

Re: Fearless concurrency with Rust

#23
post #17

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 possibl…

> 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...

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.

Re: Fearless concurrency with Rust

#24

Earlier quoted context omitted.

> 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...

Deadlocks can be statically prevented by ensuring that all code paths acquire locks in the same order. However, Rust's type system is not capable of enforcing that by itself (except maybe in some very limited cases). Some other substructural type systems (e.g. http://en.wikipedia.org/wiki/Substructural_type_system#Order... ) might be up to the task, but I'm far from an expert in this area.

Ah thank you! This is super neat.

Re: Fearless concurrency with Rust

#25
post #23

Earlier quoted context omitted.

> 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...

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…

Ah, thanks for that, I'll look into it.

I guess what I was trying to get at is that 'race conditions' can fall under the same kinds of errors as logic errors. This is the way that people usually attack the phrase "if it compiles, it works."

Re: Fearless concurrency with Rust

#26
post #23

Earlier quoted context omitted.

> 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...

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…

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.

Re: Fearless concurrency with Rust

#27
post #8

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…

> 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/ 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", sin…

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.

Re: Fearless concurrency with Rust

#28
post #23

Earlier quoted context omitted.

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…

Ah, thanks for that, I'll look into it. I guess what I was trying to get at is that 'race conditions' can fall under the same kinds of errors as logic errors. This is the way that people usually attack the phrase "if it compiles, it works."

I'm not sure what you mean when you say that race conditions can be the same kind of error as logic errors.

I would define a race condition as "an error due to nondeterministic concurrency". If you ban nondeterminism, for example, race conditions are impossible. In practice, nobody wants to ban nondeterminism, so races creep in. There might be smarter ways to eliminate races (ways of only allowing benign nondeterminism), though. So I'm not sure that practical languages without race conditions are an unachievable goal, just a difficult one. Give it another 20 years and we'll see where we're at. :)

Re: Fearless concurrency with Rust

#29
post #12

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 own…

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.

Re: Fearless concurrency with Rust

#30
post #14

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.

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.

I take it there's no way of tying the lifetime of the MutexGuard to the lifetime of the .access return, so that you don't have to have this intermediate step?
Post reply on HN