Rust for Clojurists
gist.github.com
gist.github.com
1. No explicit types: Everything in Clojure is just a list or hash map, so I can add extra members to anything at runtime no problem. The amount of static checks Rust does at compile time makes this non viable.
2. Fully immutable data structures: If my changes cause exceptions, then the program can just rewind and throw away the new state it was building, let me fix the error I made, and then continue on as if I never committed the broken code. In Rust an exception would mean I've potentially mutated state in a bunch of places, and now can't get back to the previous working state at all.
I'm used to using the REPL in F#, but you can't redefine data types and have old code pick up on it - only new code. Such an approach is theoretically possible in Rust. Debuggers can do this for C++ to some extent.
Also, Scheme and CL allow free reign on data mutation and RDD/HS work fantastically in them
You can certainly have a hash map of trait objects in Rust (it just points to the vtable and the data). This means anything that has an impl for the Trait can be stored in the hash map.
Rust also doesn't have exceptions, using algebraic data types to hold on to the errors and return them up the stack. Because it's on the stack, it's not some global state that's mutated, but we just went back to the calling function and we can attempt to try again.
"Many people try to compare Rust to Go, but this is flawed. Go is an ancient board game that emphasizes strategy. Rust is more appropriately compared to Chess, a board game focused on low-level tactics. Clojure, with its high-level purview, is a better analogy to the enduring game of stones."
Nice :)
> You will remain a replaceable silhouette with no discernible identity.
> This isn't C or C++, where you just include files like a caveman.
>The upside is, you will curse it at compile-time instead of at runtime. The downside is, "exploratory programming" means exploring how to convince the compiler to let you try an idea.
After that it gets a bit more serious, but still a very good Saturday afternoon read.
/r/2meirl4meirl
But in all seriousness, this was a great article and an exceptionally fun read!
I was running into serious issues with the following code:
https://gist.github.com/l1x/5678c0fdfc2c1a6034b8bcc3800de93c
Its performance is saturated around 60K req/s on a 32 core box with 10G networking while wrk was more than happy to do 3M req/s. I was trying to dig further into why it is slow and it seems that hyper is the culprit. However, I was not able to come by this issue and just used wrk2 for testing. Let me know if anybody has insight into how to go beyond this perfroamance or what are the issues with my code.
[package]
name = "hammer"
version = "0.1.0"
authors = ["me"]
[dependencies]
hyper = "0.10"
[profile.release]
opt-level = 3
debug = false
rpath = false
lto = false
debug-assertions = false
codegen-units = 1
panic = 'unwind'
EDIT: From wrk's README:
> wrk is a modern HTTP benchmarking tool capable of generating significant load when run on a single multi-core CPU. It combines a multithreaded design with scalable event notification systems such as epoll and kqueue.
Your example is multithreaded but IIRC release Hyper isn't using event notification async, which Tokio Hyper does. It might help.
- The standard channel library isn't bad but it's a far cry from great. You end up allocating every SEND. Which has its trade offs.
- You are doing a print in every thread which requires acquiring a global lock, and at least 2 system calls, and a global allocation.
- You aren't keeping your client/connection alive. So every new thread has to do a new 3 way handshake every request. 5 way if your is HTTPS.
Answers:
- we are not spawning a thread for each request
The following means we are creating N threads and execute a loop in every one of them, unless I misunderstand Rust entirely.
for id in 0..NTHREADS {
let thread_tx = tx.clone();
thread::spawn(move || { } ....
- I am not sure what coould I do about the standard channel library- Doing a print at the very end once, the impact on performance is negligible
- keep alive used to be broken with hyper and I am not only looking for keepalive but pipelinening as well. Let me know if you are aware of a HTTP client in Rust that can do both
It'd probably be better to continously run a core per thread and .spawn many tasks for it so that there are several concurrent connections per thread. Or maybe just .run a future which is the .join of multiple .get futures?
I'm not asking whether there are Clojurists. Of course there are.
I'm asking whether there are people that primarily and only know Clojure, and thus would benefit from a translation guide between Clojure and Rust, instead of say between C or Java or Python or Ruby etc and Rust.
I'd think that anybody who uses Clojure was already familiar and/or proficient in some other language before, much more major than Clojure.