Hacker News new | ask | show | jobs
by kaba0 1159 days ago
Come on, Rust has a good type system, but it’s nothing special - Haskell has the same guarantees since decades and it’s not like Haskell programs are suddenly without fault. There is only so much static types can catch without dependent typing, as per Rice’s theorem, types are a ‘trivial property’. Most of the interesting stuff happens at runtime and those are much harder to reason about.

Just because you have a Result type doesn’t mean you actually properly/meaningfully handle the error at all, it may just happen that “restart” is the correct solution. Also, Rust is not safe from dead/live locks and many other concurrency issues, only data race free.

This is not against Rust, but against the very biased hype for it.

1 comments

> it’s not like Haskell programs are suddenly without fault

I never said that. But would you claim that Haskell programs are generally more faulty / less stable than Erlang programs?

> Just because you have a Result type doesn’t mean you actually properly/meaningfully handle the error at all

Okay, but the same is true for Erlang and the BEAM.

> it may just happen that “restart” is the correct solution

Yeah, but is very easy to do with Rust and Haskell or even just on the infrastructure level (i.e. restart the failed container/instance).

> Also, Rust is not safe from dead/live locks and many other concurrency issues, only data race free.

How is Erlang safe from those things in a way that cannot or only with a lot of effort be replicated when using Rust?

> > Also, Rust is not safe from dead/live locks and many other concurrency issues, only data race free. > How is Erlang safe from those things in a way that cannot or only with a lot of effort be replicated when using Rust?

Each BEAM process (other runtimes would call them preemptable green threads) has its own heap, communicates with other processes by messages, and only works on immutable data without the involvement of Native Interface Functions (NIFs). There’s no shared memory at all. It is natively massively concurrent without most of the risks involved. Sure, it’s possible to write code that results in mailbox deadlocks, but it also means stepping outside of normal program design for the BEAM, and it means stepping outside of OTP (which provides structured programming constructs like gen_server).

Rust makes it harder to program without memory safety. Erlang (and other BEAM languages) make it harder to program without concurrency safety.

Edited to add:

Joe Armstrong’s thesis on Erlang is available for download and is written in very accessible language[1].

If you want more, you can also read Programming Erlang (2nd Edition)[2] also by Joe. (I would love to see a 3rd edition tackled by someone to address the newest stuff added in the 9 years since the last publication. I can understand why no one would want to approach this, since it was Joe’s.)

[1] http://erlang.org/download/armstrong_thesis_2003.pdf [2] https://pragprog.com/titles/jaerlang2/programming-erlang-2nd...

> Each BEAM process (other runtimes would call them preemptable green threads) has its own heap, communicates with other processes by messages, and only works on immutable data without the involvement of Native Interface Functions (NIFs). There’s no shared memory at all.

Is this related to my question?

I can see that this helps to prevent (or ease) out-of-memory errors. Other than that, what's the difference to using Rust's green threads, given that the developer knows what they are doing (but are still human and can make mistakes of course)?

Maybe a concrete example would help me (and others) to understand the difference.

> Other than that, what's the difference to using Rust's green threads, given that the developer knows what they are doing (but are still human and can make mistakes of course)?

An analogy: The BEAM process model is to "developers know what they are doing" as the Rust borrow checker is to "C developers know how to write memory safe code."

In other words, your program will have bugs. Your program will not correctly handle every failure mode. Your program will fail. The BEAM process model makes it so that a failure in one process won't take down all processes. And furthermore, after a process fails, there's always a deterministic way to recover from that failure without you the programmer having to think about it too hard.

The system is so robust that having processes fail on error conditions is encouraged. Once you really internalize this "let it crash" way of thinking and writing code, you only program for the happy path and let the process system handle the rest. The code ends up being much shorter and easier to understand. It's the complete opposite of writing code in something like Go, or I imagine Rust, where you explicitly handle or punt every error you can think of at every step.

And interestingly enough, despite not handling errors at every step, the share-nothing process model ends up being much more resilient in the face of errors.

> The BEAM process model makes it so that a failure in one process won't take down all processes

Yeah. But the deveveloper still decides on how many and what processes there are. They have to understand the concept of a process and spawn them accordingly.

The same is true for Rust Tokio (and similar solutions) as well - you have to create tasks and manage their lifecycle.

For example, if you were to implement an http server, you'd have to use one erlang process per request so that if something goes wrong it only impacts this request and doesn't kill the server. In Rust, you would create a Task (green thread) per request as well, which then (if it fails) will not impact that Task that is "supervising" and creating those per-request-tasks, no matter if the request fails for a "valid" reason or because of a bug like an endless loop.

And even if there is memory and CPU resources (even OS threads) shared between those tasks, they are logically separated and for the developer it only matters in very rare cases (such as OOM errors).

I'm not saying that you get exactly the same level of fault tolerance or convenience with Rust here but I also don't see the fundamental difference. Hence, I feel your analogy would only make sense, if the developer has to work without a Task/Greenthread library.

It is completely the answer to your question.

Note that Rust does not have green threads (RFC 230: https://github.com/rust-lang/rfcs/blob/master/text/0230-remo...), so without using the coroutine crate (which most developers don’t know how to use; the truth is that most people don’t know how to use threads).

The features that I talked about have nothing to do with preventing out-of-memory errors—they don’t really help with that. For non-external resources, the features described prevent memory contention (no shared memory).

Much like it’s hard to understand the Rust borrow checker quickly, the "example" you’re asking for is not possible in a comment on HN. I recommend looking over Joe Armstrong’s thesis, for which I provided you a link.

> Note that Rust does not have green threads

Rust has Tokio, which does have green threads.

From https://docs.rs/tokio/latest/tokio/task

> A task is a light weight, non-blocking unit of execution. A task is similar to an OS thread, but rather than being managed by the OS scheduler, they are managed by the Tokio runtime. Another name for this general pattern is green threads.

Hence I don't see a fundamental difference here.