News for hackers. Stuff that matters.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by Waterluvian on Thursday June 02, 2022 @08:13PM (#31602369)
I’m a Rust newbie. Mind if I ask: are you referring to using threads and locks and queues and such?

Does Rust give you rope to hang yourself when doing it without async or does it continue to be very specific about forcing you to guarantee that you’re not going to run into races and whatnot?

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by nyanpasu64 on Thursday June 02, 2022 @08:32PM (#31602518)
Rust marks cross-thread shared memory as immutable in the general case, and allows you to define your own shared mutability constructs out of primitives like mutexes, atomics, and UnsafeCell. As a result you don't get rope to hang yourself with by default, but atomic orderings are more than enough rope to devise incorrect synchronizations (especially with more than 2 threads or memory locations). To quote an earlier post of mine:

In terms of shared-memory threading concurrency, Send and Sync, and the distinction between &T and &Mutex<T> and &mut T, were a revelation when I first learned them. It was a principled approach to shared-memory threading, with Send/Sync banning nearly all of the confusing and buggy entangled-state codebases I've seen and continue to see in C++ (much to my frustration and exasperation), and &Mutex<T> providing a cleaner alternative design (there's an excellent article on its design at http://cliffle.com/blog/rust-mutexes/).

My favorite simple concurrent data structure is https://docs.rs/triple_buffer/latest/triple_buffer/struct.Tr.... It beautifully demonstrates how you can achieve principled shared mutability, by defining two "handle" types (living on different threads), each carrying thread-local state (not TLS) and a pointer to shared memory, and only allowing each handle to access shared memory in a particular way. This statically prevents one thread from calling a method intended to run on another thread, or accessing fields local to another thread (since the methods and fields now live on the other handle). It also demonstrates the complexity of reasoning about lock-free algorithms (https://github.com/HadrienG2/triple-buffer/issues/14).

I find that writing C++ code the Rust way eliminates data races practically as effectively as writing Rust code upfront, but C++ makes the Rust way of thread-safe code extra work (no Mutex<T> unless you make one yourself, and you have to simulate &(T: Sync) yourself using T const* coupled with mutable atomic/mutex fields), whereas the happy path of threaded C++ (raw non-Arc pointers to shared mutable memory) leads to pervasive data races caused by missing or incorrect mutex locking or atomic synchronization.

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by megous on Thursday June 02, 2022 @09:25PM (#31603083)
Re: fearless concurrency... Would Rust prevent you in general from writing code that could deadlock, btw?

Thread1: takes lock A, ..., tries to take lock B

Thread2: takes lock B, ..., tries to take lock A

Looks like you should be able to pass Mutex<A> and Mutex<B> to both threads otherwise what's the point of mutex if there's no way to share data protected by it, so it doesn't look like it prevents you from hitting this scenario.

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by tialaramex on Thursday June 02, 2022 @10:04PM (#31603363)
No, Rust doesn't prevent deadlocks, a deadlock is safe (it isn't what you wanted, but it's safe). There are well-known strategies to avoid deadlock (in any language)

In the trivial example you gave, one strategy just insists we take locks in alphabetical order. Thread 2 can't take lock A, because it already has lock B and that's not the correct order.

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by jandrewrogers on Friday June 03, 2022 @12:16AM (#31604228)
No, deadlock-free code requires some additional structure. There is no general way that I know of to prevent deadlocks in any software with non-trivial lock graphs, but there are standard techniques to detect deadlocks programmatically so that they can be broken and resolved. OLTP databases figured out how to do this decades ago, but those techniques are expensive for general purpose programming.

The common method for deadlock-free code is roughly that when a thread is required to wait on a lock owned by a second thread, it checks to see if the second thread is waiting on a lock already owned by the first thread. This requires that the lock graph essentially be a high-performance and concurrent global structure.

Locks like this can be expensive, particularly under high concurrency or contention, so they aren't used for most software. If you can fit your software in a simpler model e.g. where locks are singular or only acquired as a DAG, then much higher performance options are available that don't require deadlock detection.

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by bombela on Thursday June 02, 2022 @09:51PM (#31603273)
Rust doesn't protect you against deadlock.

I wonder if it is similar to the halting problem. Can deadlocks even be prevented in theory?

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by mjn on Thursday June 02, 2022 @10:43PM (#31603631)
In the general case you're right, it's equivalent to the halting problem. The outline of the proof by reduction: set up two communicating processes in a way that will deadlock iff a particular loop in one process fails to terminate. So if you had a deadlock detector for arbitrary communicating processes, you could use turn it into a termination detector for arbitrary loops.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by jandrewrogers on Friday June 03, 2022 @12:30AM (#31604309)
Yes, deadlock-free lock systems are an ordinary part of OLTP database engines. They don't prevent deadlocks per se so much as detect them and dynamically resolve them.

The mechanism is costly but elegant. If a lock you are trying to acquire is owned by another thread, you inspect the locks you own to determine if that thread is waiting on one of your locks. When a deadlock is detected, there are several strategies to automatically resolve it e.g. rolling back one of the threads to a point where forward progress can be safely serialized.

No one wants to use these mechanics for ordinary code, due to their cost. For the fashionable thread-per-core software architectures, deadlocks aren't something you commonly have to worry about.

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by Waterluvian on Thursday June 02, 2022 @10:31PM (#31603547)
Only if the priority is flat and not some sort of graph, is my guess.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by tick_tock_tick on Thursday June 02, 2022 @10:32PM (#31603555)
I'm pretty sure "fearless concurrency" is just a meme at this point since Rust does very little to make concurrency "fearless".
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by renox on Friday June 03, 2022 @04:00AM (#31605499)
There are two kinds of threading bugs: deadlocks which are easy to detect and race conditions which are far more difficult to detect and to fix. AFAIK Rust helps with the latter not with the former which is a very big improvement (much more than if it was the other way round)
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by kaba0 on Friday June 03, 2022 @07:05AM (#31606748)
Just to add, AFAIK Rust only prevents data races, not race conditions in general. Which is still a huge help, but concurrency is still hard without a much more restricted model.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by jjnoakes on Friday June 03, 2022 @01:12AM (#31604533)
Very little except statically removing an entire class of bugs that plague other languages in the same design space you mean.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by Waterluvian on Thursday June 02, 2022 @10:28PM (#31603534)
I really appreciate this detailed response. You gave me plenty to base my searches and reading on.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by pjmlp on Friday June 03, 2022 @04:50AM (#31605824)
Yeah, but it doesn't help on shared-memory process concurrency, and we all know that in 2022 the best way to ensure secure software is to go back to processes.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by jacksnipe on Thursday June 02, 2022 @08:25PM (#31602451)
Oh my god we’ve strayed so far from the light T_T

Once upon a time, the whole promise of Rust was “fearless concurrency”.

Re:Rust Is Hard, Or: The Misery of Mainstream Programming by infogulch on Thursday June 02, 2022 @10:36PM (#31603583)
I don't understand. What about this thread would make you fear running rust? Fear of learning a solution to rusts memory model, and 'fighting with the borrow checker' as they say -- sure. But rust never claimed that you wouldn't have to climb a steep learning curve, it claims that if you do make it up then the result can't hurt you due to a memory fault. So "fallen from the light" seems a little overdramatic.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by charlieflowers on Thursday June 02, 2022 @11:29PM (#31603942)
IMO fallen from the light sounds spot on (not gp). I’m shocked to hear the state of async from real practitioners saying to avoid it.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by infogulch on Friday June 03, 2022 @01:16AM (#31604551)
"fallen from light" would mean breaking it's safety promises. No promises have been broken, the async developer experience is just less than a silver bullet and needs a lot of work. If you're so disappointed by that fact that you'd say rust has "fallen from light" then you've not been paying attention at all or your expectations need serious calibration.
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by accountofme on Friday June 03, 2022 @01:18AM (#31604563)
Fearless concurrency in respect to threading .... Not async
Re:Rust Is Hard, Or: The Misery of Mainstream Programming by ericbarrett on Thursday June 02, 2022 @09:37PM (#31603176)
Yes, Arc for structured data passed between threads, atomics for smaller things like counters job queue length. Of course the fastest synchronization primitive is nothing at all :)