News for hackers. Stuff that matters.
Re:Sayonara, C++, and Hello to Rust by berkut on Tuesday December 07, 2021 @03:44AM (#29470306)
Definitely for prototyping, as someone with years of 'advanced' C/C++/Python experience, and someone trying to learn Rust, I'm finding Rust's 'opinionated' ways of doing things very painful for things like fast prototyping, where you need to impl 'default' traits on structs, or create builders (which is almost as bad as C/C++'s split of declaration and implementation in that you have tightly-coupled things in different parts of the code.), sticking unwrap() everywhere to not handle all errors correctly at this stage, etc, etc. i.e. you don't really know what you want, seeing what works, how the data could be organised, refactoring in the early stages, and so much is in flux at this stage, the compiler's warning about all sorts of things, but I don't want to fix them as it might be a waste of time at that stage if you change things yet again, so just end up shoving: #[allow(dead_code)] above all enums or something, so I can actually see errors within all the warnings.

Once you move past the prototyping stage on to full on production coding, I totally buy that in a lot of cases, the safety that Rust provides (if it compiles, it will almost certainly either panic or work, ignoring logic bugs), is a big win, but I'm not convinced yet for experimentation when prototyping...

Re:Sayonara, C++, and Hello to Rust by littlestymaar on Tuesday December 07, 2021 @04:02AM (#29470401)
I personally love Rust for prototyping for one particular reason: even if it's not as quick as what you'd do in dynamic languages (JS for me, for most people it would be Python) it comes with an enormous benefit: iterating on your prototype several times until you eventually reach the final version is much, much quicker than in any other language I know, simply because the compiler will catch almost any mistakes you make during your refactoring.

So, The first draft is going to take like twice as long, but every subsequent iteration on your prototype will be much faster. I find the payoff time to be pretty quick (like one or two iteration are often enough), but obviously if you're doing a true one-shot that you're never gonna touch again, it won't be worth it (that being said in my experience, such situations are pretty rare and most one-shots end up being way more than what they where supposed to in the first place).

Re:Sayonara, C++, and Hello to Rust by berkut on Tuesday December 07, 2021 @04:06AM (#29470425)
You can argue that (and I'd generally agree in most cases above a certain complexity size) about dynamic vs any static language though?
Re:Sayonara, C++, and Hello to Rust by littlestymaar on Tuesday December 07, 2021 @04:49AM (#29470699)
Yes, it's exactly the static vs dynamic trade off, but with one of the best static programming language design.

C++ is probably at the other end of the spectrum since it's too easy to accidentally cause a memory error during refactoring. C# and Java are a bit in-between, the automatic memory management limits the footgun, but the type system is much less powerful than Rust's one so there still a lot of issues that can come during a refactoring. I have only very limited OCaml experience and no Haskell experience but I think you'd have most of Rust advantages with those, except the thread-safety part which is my favourite part of Rust but is also situational.

Re:Sayonara, C++, and Hello to Rust by colonwqbang on Tuesday December 07, 2021 @07:36AM (#29471736)
Haskell is quite thread safe, I suppose it is at least as good as Rust in this respect. Immutability by default and garbage collection really help to cut away the ceremony around threading.
Re:Sayonara, C++, and Hello to Rust by tialaramex on Tuesday December 07, 2021 @08:56AM (#29472461)
> I suppose it is at least as good as Rust in this respect.

(Safe) Rust has Data Race Freedom, which means you get Sequential Consistency. As I understand it, Haskell does not promise you data race freedom, so, if you mistakenly implemented a data race now you don't have Sequential Consistency and now you can't reason about non-trivial programs at all.

I think Haskell, like Java, and unlike C or C++, promises you still get a meaningful program if you have a data race, but it may be tricky to debug the mess.

Edited to expand: Suppose you're writing a program to process 16GB of data, the result will be a floating point value between 0 and 100.0 as a result of this processing. You realise it's embarrassingly parallel and so you spin up 16 threads to solve the problem in parallel on your CPU cores.

Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads, the processing involves a lot of twiddling data, and so now it's full of data races. What is the result of your buggy program?

In Rust, the program doesn't compile, or depending how you wrote it, exits immediately reporting that you can't go around processing the same data from different threads.

In C or C++ maybe it deletes all your data, more likely it simply exits instantly with no result, spins forever, or outputs something completely spurious.

In Java you might get an Exception reporting that you mustn't do this, but you might get a bogus but real looking answer like 46.2 now good luck figuring out why.

I don't know for sure about Haskell, but I think you get a similar situation to Java minus the exception.

Re:Sayonara, C++, and Hello to Rust by zozbot234 on Tuesday December 07, 2021 @02:10PM (#29476859)
> (Safe) Rust has Data Race Freedom, which means you get Sequential Consistency.

I don't think this is correct. Safe Rust includes atomics that are data-race free but need not be sequentially consistent.

Re:Sayonara, C++, and Hello to Rust by tialaramex on Tuesday December 07, 2021 @06:29PM (#29479519)
Unsurprisingly core::sync::atomic is not in fact implemented in Safe Rust.

However you can indeed use it from Safe Rust, and you can choose Orderings which do not give you Sequential Consistency if you're comfortable with that, or more likely because you are implementing an algorithm whose authors assure you this is fine (I hope they're correct). I think that's a very modest caveat to the overall promise of Sequential Consistency, but sure, it is a caveat. If you write code that explicitly asks for Ordering::Relaxed and are astonished you don't get Ordering::SeqCst I guess I apologise for confusing you.

Re:Sayonara, C++, and Hello to Rust by pjmlp on Tuesday December 07, 2021 @01:41PM (#29476476)
On the other hand, if one decides to use 16 processes to handle the data across a NUMA shared memory segment, to be able to easier scale it across the cluster, the Rust type system is of little help.
Re:Sayonara, C++, and Hello to Rust by tialaramex on Tuesday December 07, 2021 @06:52PM (#29479721)
To make this work you're going to need an (unsafe) wrapper that lets all your processes get write access to the same 1GB of shared memory. If you write a wrapper that ensures safety, you'll get back to the same place where the faulty program reports that you can't all map the same 1GB of data and twiddle it, and if you don't it's on you for introducing unchecked unsafety.
Re:Sayonara, C++, and Hello to Rust by pjmlp on Wednesday December 08, 2021 @01:34AM (#29481863)
Assuming every single OS process that has access to the NUMA segment is written in Rust and originates from exactly the same build, which is impossible to represent on the type system.
Re:Sayonara, C++, and Hello to Rust by juxtapose on Tuesday December 07, 2021 @08:01PM (#29480192)
> 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