Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Shared objects interacting across threads is a bad idea. Java did it “safely” forever ago, it just throws a lock on everything


Data sharing between threads is inherently too much of a complex model for programmers to manage (when systems get complicated enough), that in many cases it is better to think of a solution that avoids it altogether. This is why some concurrency-centric languages (Erlang, Go) choosed to use message passing as the main paradigm, instead of going for locks everywhere at runtime (Java) or an incredibly complex type system that tries to prevents data races at compile time (Rust)...


Rust's type system for thread safety is actually remarkably simple. Types declare whether they add or remove thread safety (e.g. Mutex adds safety, non-atomic Rc removes). Structs automatically become non-thread-safe if they have non-thread-safe fields. Then all the functions that spawn threads or send data over channels require thread-safe types.

The fearless concurrency is real. It reliably prevents data races, use-after-free, and moving of thread-specific data to another thread. It works across arbitrarily large and complex call graphs, including 3rd party dependencies and dynamic callbacks. Plus immutability is strongly enforced, and global mutable state without synchronization is not allowed.

It doesn't prevent deadlocks, but compared to data corruption heisenbugs, these are pretty easy — attach a debugger and you can see exactly what deadlocked where.


> global mutable state without synchronization is not allowed.

That's the java model again. I don't want fearless concurrency, I want intentionally designed threads.


It's not the Java model because Java just makes every operation atomic (from the point of view of the model, I'm sure the JVM and javac must do optimizations to avoid some of them), while Rust enforces that multi threaded access must be through atomic operations, but if there is no multi threaded access or the type is not meant to be used in a multi threaded context, that information is encoded in the type system. This might sound like an academic distinction, but it is different: the developer is in control. You could even go as far as lie to the type system and claim a racy type is actually thread safe. I wouldn't advice doing so, but I can't stop you.


I’m sure the rust version is more ergonomic. It’s great you can do it more safetly. But it’s a bad application design from the start.


That's an incredibly broad criticism aimed at some hypothetical solutions you imagine, not grounded in what Rust does. No language can stop an imaginary infinitely determined fool.

Rust's restrictions, such as strict scopes of references and strongly enforced shared XOR mutable access, prevent many sloppy and careless designs that are possible in Java or C++.

Rust also takes advantage of its type system, generics, and ecosystem to offer solid constructs for multi-threading. There are safe data parallelism libraries, task queues, thread pools, scoped threads, channels, etc. Users are well equipped to implement multi-threading properly, and as much as possible Rust steers users towards locally-scoped, immutable or share-nothing solutions.


Message passing is not any easier or safer though. For every problem with shared memory concurrency you can draw a dual problem in message passing. See: https://songlh.github.io/paper/go-study.pdf

For me, single-threaded intra-task concurrency using async/await turned out to be safer and also easier to work with than either of the above mentioned concurrency models. Just a single loop with a top level select - everything is sequential and easy to reason about, also no need for any synchronization like locks or shared atomic pointers.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: