> I think Haskell, like Java, and unlike C or C++, promises you still get a meaningful program if you have a data race

This is true (when you insist on mutable shared memory), but in Haskell the safety guarantee is in fact not so far from Rust. The Haskell community is well aware of concurrency issues very early, and they developed a dozen of composable abstractions to solve these problems. (IMHO Rust is the only one that has a truly novel solution in recent years.)

In Haskell, either you have to use `unsafe` functions to share bulk of memory or you don't share memory at all (as mutability is a pita in Haskell). On top of these unsafe functions, you can build other safe abstractions, just like Rust. These abstractions are often available as higher-order functions in libraries, making them highly composable and reusable.

E.g., the canonical memory-sharing data type is `TVar`, which must be access from the `STM` monad [1], guaranteeing both atomicity and data race freedom. (BTW, STM stands for software transactional memory if you aren't familiar with it.) A major offender is `IORef`, and fortunately Haskell provides atomic operations on `IORef`s.

GHC Haskell also offers free parallelism for all pure computations via the `Eval` monad [2].

> Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads

This is highly unlikely in Haskell. Haskell emphasizes on composable combinators, so a program that does not share memory would probably look like

foldr accumulateFunc initValue . mapConcurrently processBatch . split batchSize $ data

(mapConcurrently here is a real function [3].) I'm sure C++, Java, and Rust have similar abstractions, but imperative languages make it too easy for programmers to do `for` loops, making it much more error-prone.

Several array libraries provide safe parallelism. The interface is similar to Rust rayon, but the way they work differs greatly. For example, in Repa [4] you can do

computeP $ traverse inputArray newShapeFn (\getElem curIdx -> ...)

Here `traverse` results in a "delayed" array: the results are not immediately available, and what you are doing is just composing array operations. `computeP` forces the computation to happen _in parallel_. Since GHC is very capable of optimizing intermediate data structures away, this code can compile to very efficient machine code -- there is no mutability at all, yet you get very safe and efficient parallelism.

PS: what Haskell is really bad at so far is safe in-place updates for arbitrary data types (like custom ADTs), which is solved by Rust cleanly. GHC 9.0 added Linear Types [5] to address this problem. Haskell and Rust are both bad at safe in-place updates of arrays, vectors, etc., and are likely to be bad for a long time -- their indices are arbitrary integers, which cannot be analyzed statically at compile time. Haskell probably can solve this problem completely when it gets dependent types [6], whereas Rust is unlikely to support DT any time soon, if ever [7].

[1] https://hackage.haskell.org/package/stm-2.5.0.1/docs/Control...

[2] https://hackage.haskell.org/package/parallel-3.2.2.0/docs/Co...

[3] https://hackage.haskell.org/package/async-2.2.4/docs/Control...

[4] https://hackage.haskell.org/package/repa

[5] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...

[6] https://github.com/ghc-proposals/ghc-proposals/blob/master/p...

[7] https://github.com/rust-lang/rfcs/issues/1930