Tooling, Tooling, Tooling
There’s the old adage that there are three important things for a house: Location, Location and Location. Similarly, there are three important things for a programming language: Tooling, Tooling and Tooling.
(One key aspect to Java’s success by the way, is exactly the phenomenal tooling that comes with the JVM. But I digress.)
This point was recently driven home for me following the tracks of the parallel conference 2019 – we have made awesome strides in simplifying parallel programming by adding layers of abstraction (from threads and semaphores to tasks and high-level management to parallel streams), but we lose a bit of transparency and debuggability on each step.
For example, when you try to debug a parallel stream in Java, you lose the connection between the original stream call and the current action of any thread because the work stealing machinery gets in the way. I believe that rayon’s parallel iterators have similar problems, though I must admit I haven’t yet found a need to debug them. I don’t even try debugging an asyc/await based program on a multithreaded executor.
There seems to be a great chance for tool vendors to chime in: Create a debugger that maintains the task abstraction and recreates the links between forked tasks to allow users to see the intended flow of the program instead of a tilted view of a mix of their code and the task executor/parallel stream API interna. This applies to all programming languages that offer a task-based abstraction, be it C, C++, Rust, Go, Java, C#, Clojure, Erlang, Elixir, Pony or whatever floats your boat.
A lot lower down the stack there’s SIMD, which I believe currently comes in three flavors: Pseudo-assembly intrinsic functions, autovectorization or some kind of library/extension in-between (offering a “nice” enough interface with somewhat guaranteed vectorized code in the result). Intrinsics get unreadable quickly, autovectorization is hard to debug if it fails to vectorize (though I hear that the intel compiler generates very nice reports to help you with that) and so we are left with the in-between (which for C and Fortran is OpenMP, for Rust is something like faster or packed_simd. I don’t know what other languages do in that space.
And again, there’s the matter of debuggability, although the (non-functional) issues at play are likely more subtle, because this time the abstractions that keep us from deeply understanding why something is faster or slower are hidden within the CPU cores and other intermediaries such as the operating system. So far we lack the tools to pierce this numerous veils; again I believe that major strides could be made, both with regards to documentation and tools to help us understand the performance implications of various code variants.
Going from performance to safety, Rust naturally has a good spot because many guarantees are there by default, but only for safe code. Luckily Shnatsel, among others, work on bringing sanitizer support and other improvements (e.g. fuzzing) to Rust, but we still have some catching up to do to get to C++ levels of tooling (on the other hand they have to rely on that kind of tooling more, because of the pervasive unsafety they have to brave). Recent extensions to miri give hope that we can get high-level support for finding and reporting unsound code, though it is as of now not entirely clear if those reassurances hold up once LLVM is done with the code.
Of course all that doesn’t matter unless the program compiles in the first place. And rustc is one of the most finicky compilers out there. On the other hand, it’s also one of the most helpful ones in terms of error messages, warnings and suggestions. This area sees a steady stream of big and small improvements, and planned extensions will help to unlock even better compiler UI (for example, Chalk offers a more principled way of looking at types, giving us the chance to offer more specific error messages on type errors).
Library authors are using the immense power of the type system, which may exacerbate usability if error messages get weighed down by humongous type signatures rivaling template error messages in C++. It remains to be seen if we can find ways to handle the complexity faster than we’re piling it up.