top | item 45793041

(no title)

lucasoshiro | 3 months ago

> Clojure solves the same problems Rust solves in a much simpler way

I'm curious about that, can you elaborate? I'm a beginner in Clojure and I only know a few concepts about Rust, but it seems to me that they solve (at least, currently) very different problems...

I only saw Rust being used in places where C or C++ would be used in the past (Linux kernel, CLI apps, desktop apps), while I only saw Clojure being used as a modern and functional Java and JS replacement.

Not to mention how different they are as languages (static vs dynamic typing, native vs jvm, borrow checker vs garbage collection)

discuss

order

Zambyte|3 months ago

The main problem that Rust tries to solve, and that functional programming (which Clojure heavily leans into) solves, is avoiding shared mutable state, which leads to data races and (potentially subtly) wrong concurrent programs. Functional programming avoids shared mutable state by avoiding mutable state. Operations are only represented as transformations instead of mutations. Other languages like Erlang / Elixir use message passing techniques like the Actor Model to avoid shared mutability. Instead of avoiding mutability, like in functional programming, in the Actor Model you avoid sharing, by instead sending messages around.

Rust is interesting because it solves the problem of shared mutable state, while allowing sharing, and allowing mutability, just not at the same time. State can be mutated until it is shared. While it is shared, it cannot be mutated. This is the goal of the ownership system in Rust.

SkiFire13|3 months ago

> The main problem that Rust tries to solve, and that functional programming (which Clojure heavily leans into) solves, is avoiding shared mutable state

I would argue that avoiding _unrestricted_ shared mutable state is a mean for Rust, not a goal. The main goal would be to provide a way to make safe, fast and non garbage collected programs, which doesn't seem at all what clojure is aiming for.

codesnik|3 months ago

Well, erlang and elixir operate on almost exclusively immutable data, in addition to message passing between "processes". They are more strict than most other languages in that regard.

labrador|3 months ago

One thing that jumped out at me is you don't need Rust's borrow checker to prevent thread race conditions because data in Clojure is immutable. That's a huge simplification over Rust. What I hear mostly about Rust is complaints about the borrow checker. I wrote a simple Rust utility and found it frustrating. But again, don't take my word for it because I don't know either language.

asa400|3 months ago

As someone who has gone deep into both functional programming (with Elixir, Clojure, and Scala) and also Rust, they solve the same problem in different ways with different tradeoffs.

The problem is that shared mutable state is incredibly hard to get correct, especially when concurrency enters the picture.

Elixir and Clojure sidestep the problem by making copying data so cheap that instead of sharing references to mutable data, you make all data immutable and copy it whenever you want to change it in some way. (Which is a classic technique: don’t like the problem? Solve a different problem that sidesteps the original problem in a creative way)

So you have a lot of functions of roughly the shape `f -> f` (return a new thing) instead of `f -> ()` (mutate a thing in place).

This possible at all by the clever use of some novel data immutable structures like HAMTs that are able to approximate the performance of traditional mutable data structures like hashmaps or arrays while presenting an immutable API.

As it turns out, this is a much easier programming model for most of us to get correct in practice (especially in the presence of concurrency) than sharing mutable state in an imperative programming language like C or Java or Python.

The tradeoff is that the immutable functional data structures actually do have a some performance overhead. In most domains it's not large enough to matter, but in some domains it is, and in those domains you really do need mutable state to eek out that last bit of performance.

Which is where Rust comes in.

In Rust's model, data can either mutable or shared, but not both at the same time. The Rust compiler computes the lifetimes of data throughout your program to ensure that this invariant is upheld, so mutable data is not shared, and shared data is not mutated.

The downside of this is that you have to internalize these rules and program to them. They can be tough to learn and tough to program with even after you have learned them, though it does get much easier with experience, I will say.

The upside of Rust's model is that you can have your cake and eat it too: you can keep the high performance ceiling of a true mutable data model while maintaining memory and resource safety.

In short, you can think of Clojure/Elixir as sidestepping the shared mutability problem at the cost of some runtime performance (though again in practice it is smaller than you would think), and Rust as tackling the shared mutability problem head on by transferring the cost of solving the problem to a more complicated compiler and a harder-to-learn programming model.

These are just my opinions having used both Rust and the immutable data functional programming stuff in anger. I'm not trying to present one as being better than the other, the key point is that they're both better than what came before.

bmitc|3 months ago

I would argue that F# would be the choice of language in place of Rust if you don't want to deal with Rust's type system. F# is immutable by default with good concurrency and a static type system.

jimbokun|3 months ago

This the “non-low level” code qualification.