top | item 24940838

Rust for Clojurists (2015)

88 points| galfarragem | 5 years ago |gist.github.com | reply

39 comments

order
[+] pgt|5 years ago|reply
Having built remote monitoring and firmware updating systems for high-current LiFePO4 batteries in Rust for low-level STM32 and ESP32 integrations, Clojure for backend and ClojureScript on the frontend - I can corroborate this.

The dynamism of Clojure and the memory safety of Rust are a strong combination, especially for streaming EDN data over websockets to Clojure or ClojureScript frontend.

However, neither Rust nor Clojure is the end-game. Eventually all languages will IMO adopt memory lifecycle management (lookahead guarantees) and borrow-checking as language features to do runtime optimization.

An interesting language in this regard is Carp, a high-performance statically-typed Lisp implemented in Haskell with borrow-checking: https://github.com/carp-lang/Carp

It's a shame they did not opt to follow Clojure syntax more closely, or it would make the transition much easier for the growing Clojure community. Clojure seems to be winning the Lisp language wars.

Rust could have be so much more if it used S-expressions or M-expressions. The macro language is an abomination compared to Lisp, but I understand they had to lure the embedded C crowd.

[+] timdeve|5 years ago|reply
Love Carp, the fact that it compiles to C means it can run everywhere like embedded (GBA[1], ATSAMD51[2], esp8266[3]) but is also expressive enough that you could build web apps with it[4].

It's very pre-v1 though so expect breakage and API change.

[1] https://github.com/TimDeve/dino/blob/master/main.carp

[2] https://github.com/TimDeve/carpgamer/blob/master/main.carp

[3] https://github.com/TimDeve/carp-lang-arduino/blob/master/mai...

[4] https://github.com/TimDeve/koi/blob/master/examples/notes-js...

[+] didibus|5 years ago|reply
> Eventually all languages will IMO adopt memory lifecycle management (lookahead guarantees) and borrow-checking as language features to do runtime optimization

Personally I'd be more interested in CPUs adding dedicated GC chips such as this paper: https://ieeexplore.ieee.org/document/8695831

> It's a shame they did not opt to follow Clojure syntax more closely, or it would make the transition much easier for the growing Clojure community

I've been keeping an eye on Carp, but never tried it, at first glance it looked like they used the Clojure syntax, what part do they deviate from?

[+] lmilcin|5 years ago|reply
How come, I have the same stack. STM32 with Rust. Mqtt, Clojure for backend and ClojureScript with re-frame for frontend. Couldn't be happier.
[+] diggan|5 years ago|reply
Nice article in general, but seems to miss the most important point (at least for me) of why Clojure (and similar languages) are way better than anything else today: it's REPL driven development. Definitely more important for me than any of the other "why we love Clojure" points.

As long as Rust doesn't offer something similar as the REPL driven development Clojure offers, there is zero reason of preferring Rust (for me) over Clojure for the same tasks. Which, because of the last point ("Rust is not homoiconic") won't happen any time soon.

Now, if you need to do embedded system development, Rust might make more sense than Clojure, but it won't replace Clojure for the same tasks today.

[+] LandR|5 years ago|reply
RDD is the one big thing that really blows me away in Clojure. I now feel quite frustrated having to work in other languages where I'm missing the tight REPL integration.

It's ideal for getting into "flow", iterating on ideas, there's no waiting or downtime that you can get in other languages.

I don't know why REPL driven development and Clojure type REPL integration isn't how we all want to do development (assuming as you say it's not embedded or some other niche area). It's just so powerful as a tool for getting immediate feedback and allowing a tight, painless iteration on ideas.

[+] eyelidlessness|5 years ago|reply
I don’t know. I worked in Clojure for several years, and very seldom hit the REPL. I spent most of my exploration time in tests. In TypeScript, I split most of my exploration time between types and tests. Occasionally I hit the Node REPL, or the Chrome console. But doing speculative work in an editor, with all its features, has always felt a lot more productive to me. Maybe that’s why I ended up drifting away from Clojure anyway.
[+] creese|5 years ago|reply
I've found I can get the same tight feedback loop at least for the times where the code will not compile. I use Cargo mode and an after-save hook so that each save runs `cargo check` and, on success, also runs `cargo fmt`. A nice side effect is that I don't need to care about formatting any more.

  (defun cargo-process-check-sentinel (process event)
    (when (string= "finished\n" event)
      (rust-format-buffer)))

  (defun my-after-save-hook ()
    (when (eq major-mode 'rust-mode)
      (set-process-sentinel (cargo-process-check) 'cargo-process-check-sentinel)))

  (add-hook 'after-save-hook #'my-after-save-hook)
[+] ragnese|5 years ago|reply
Have you ever tried playing with OCaml? It's obviously not the same thing as a LISP, but its type system is similar to Rust (Rust was largely inspired by OCaml) and it's (much) more amenable to playing in the REPL.
[+] lbj|5 years ago|reply
"Many people try to compare Rust to Go, but this is flawed. Go is an ancient board game that emphasizes strategy."

This cracked me up so hard I can no longer deny being a straight up nerd.

[+] jnxx|5 years ago|reply
What I also found is that Racket is a very good match to Rust, in a similar style as writing Python and extending it with translated C code where speed is needed. The advantage is that both languages allow for a functional style and Racket is much faster than Python.

Racket is, almost like Clojure, highly interactive (though not completely so, it has a conceptual separation of compile time and run time), and on top of that, it can call easily into a C ABI, which Rust provides. Both languages (Rust and Racket) are well-suited for writing pure functions returning immutable results which allows for a nice match in style. Calling across the language boundary is a bit slower than in the Python/C case but it can achieve a decent speed-up. Racket's places allow for parallel execution with almost separate memory images, and it also has green threads. And Racket has batteries such as a GUI toolkit and image data types included which is really nice.

[+] usgroup|5 years ago|reply
“Learning Rust will probably not do much to solve that problem for you. It won't assist you in making the ontological leap from a tired stereotype into something sentient and real. You will remain a replaceable silhouette with no discernible identity. It might even exacerbate the problem.“
[+] tptacek|5 years ago|reply
I'd have assumed a primer on Rust for Clojure programmers would spend a lot of time on what a pain closures in Rust (necessarily!) are. :)
[+] jolux|5 years ago|reply
I was interested in Rust for the strong type system and borrow checker and such as well as praise from functional programmers I trust but my experience with trying to do FP in Rust was so dreadful that I put it down and stopped learning it. Perhaps I will need it badly enough to push through this someday.
[+] LandR|5 years ago|reply
This is wonderfully written :)
[+] BenGosub|5 years ago|reply
Indeed, a fantastic read, even though I am not a Clojure programmer
[+] manishsharan|5 years ago|reply
Dumb question from a curious Clojure guy : how terse is Rust codebase for non-trivial cases?
[+] speed_spread|5 years ago|reply
It can be quite terse for a typed imperative language. Macros help a lot with boilerplate.

Type inference also helps a lot but is standard in most imperative languages nowadays, so I wouldn't count it as a terseness advantage anymore. Also, Rust doesn't go full Hindley-Milner as types still have to be declared in function signatures, by design, for readability.

Generics are interesting and can save some typing when doing "blanket implementation" of traits, but overall still will count as stuff you wouldn't have to deal with in a more dynamic language.

The remaining sore point are the lifetimes annotations. Although most are elided by the compiler by now, if you fall into a pattern where you have to explicitly carry lifetimes around, things get tiresome real quick. At that point, I usually take it as a sign that my code is not as optimal as it could be, and try to rewrite in a way that's less verbose. This is very much compiler-centric approach and actually reflects the whole Rust experience where programming is akin to having a dialectic conversation with rustc, which although quite smart can also be very pragmatic in its ways.