I've built a fair amount of production software in Rust. The best features are:
- Rust is naturally pretty fast, on the rough order of C++ in many cases. Even unoptimized programs tend to be very snappy (as long as you remember to compile in release mode and buffer I/O).
- Rust tends to warn me if I make a dumb mistake, rather than having my program mysteriously corrupt memory at run-time.
- Rust has very nice support for portable CLI applications, both in the standard library and in third-party libraries.
- With a bit of extra work, I can usually deliver a single, statically-linked Linux binary. Go is even better at this, but Rust does it well.
- It turns out the "algebraic data types", what Rust calls "enum" and "match", are just a really nice way to write down every possible "case" or "state" of some value. If a piece of software involves hundreds of special cases, Rust allows me to think about them clearly.
- Rust has surprisingly good third-party libraries for many things. (Not everything.)
- Multithreaded Rust apps are a dream.
Cons:
- Rust forces me to keep track of whether things live on the heap or the stack, whether I'm passing them by value or reference, and so on. This is good when I want performance, but for other kinds of code, it's just a "cognitive tax."
- Rust's learning curve is higher than Go, but arguably lower than C++. This is especially true if you've either never worked with stack/heap/pointers before.
- Rust tends to favor "mostly functional" architectures over "mutable objet soup" architectures. This pushes you to use less-familiar designs for games and GUI libraries, for example.
> Rust's learning curve is higher than Go, but arguably lower than C++. This is especially true if you've either never worked with stack/heap/pointers before.
I tried to learn Rust when I only knew Python and had not CS education, I gave up rather quickly.
Then I did some projects in C and later on C++, and now I understand Rust.
Because I understand what are the problems it tries to solve. Imho only after you have some skills in C++, and/or some kind of CS education, can you really appreciate what Rust is about.
> Rust tends to favor "mostly functional" architectures over "mutable objet soup" architectures. This pushes you to use less-familiar designs for games and GUI libraries, for example.
One example would be the lack of pragmatic rust native GUI libraries. There are wrappers for GTK and Qt, which are probably your best option.
The most popular pure rust ones are based on somewhat esoteric patterns and seem prone to being abandoned.
Within that there are some interesting options, like for functionally reactive programming and immediate mode guis, but these paradigms aren’t all encompassing and in fact cover a pretty minority use case based on what GUIs are being created today.
Maybe one day we’ll all only ever create purely functional GUIs and speak Esperanto, but until then, we’re a little hamstrung in rust.
I think that's more a function of the newness of the language than anything else. Golang is (I say this as a Rust developer) more popular, has been around longer, yet still has the same dearth of GUI libs.
I can't speak for Go, but idiomatic Rust is not friendly towards the kinds of object graphs that are typical in traditional GUI frameworks with event handlers etc. You either end up with ARC everywhere, which is a pain because it's not transparent in Rust; or you have to come up with the aforementioned "exotic patterns", which significantly raises the bar for adoption.
Yet, web developers have been using some of those exotic patterns for ages and they don't complain. They even prefer that way to traditional GUIs by using Electron massively now. Rust GUI frameworks are typically based on strict separation between a model and a view and communication between them with messages which is something much closer to web programming than traditional GUI programming. An OOP soup with arbitrary handlers messing around with state directly in random places is IMHO a bad idea in any language anyway.
MVC originated in desktop GUIs, originally for Smalltalk. Java Swing was, I believe, the first mainstream GUI framework which used it thoroughly. Most GUI frameworks since then are also MVC (or some derivative thereof, like MVVM in .NET). This is just a way of organizing code and has little to do with presence of absence of event handlers, or the web.
It's a big piece of the learning curve, and one that usually only shows up when you try to write larger programs. Plus there's very little the compiler can do to point you in the right direction when your object soup needs major refactoring. From what I've seen, a lot of folks either give up on Rust or start writing blatantly unsound unsafe code when they hit this.
On the upside, a lot of C++ game programming uses an ECS-style architecture, which does work well in Rust.
When I tried to be more functional, it was really awkward in Rust
I wanted to write some helper functions that applied an argument to a closure and I got this code
fn apply<A, B, C, G>(mut f: impl FnMut(B) -> G, a: A) -> impl FnMut(&B) -> C
// must still be `for<'r> impl FnMut(&'r B) -> C`, because that’s what filter requires
where
G: FnMut(A) -> C,
B: Copy, // for dereferencing
A: Clone,
{
move |b| f(*b)(a.clone()) // this must do any bridging necessary to satisfy the requirements
}
needless to say, Rust doesn't do automatic currying or any advanced functional language tricks so it's just painful to write the same type of code
Yeah, your functional programming skills will probably be most useful at the achitectural level in Rust. Because mutation is very local in Rust, your individual functions can often get away with being imperative, but your overall architecture can't really rely on unrestrained mutation.
But if we zoom in from the architecture to the individual lines of code, Rust is more of a mix of functional and imperative styles. Rust's iterators can be fairly pleasant for working with lazy streams. Closures are more of a mixed bag. This is partly because closures need to worry about ownership, which complicates things by splitting closures up into Fn, FnOnce and FnMut. And each closure is a separate anonymous type. So, yeah, Rust supports quite a few useful things with closures. But as your example shows, the type declarations can quickly become intolerable.
So my strategy is to go with the flow, and keep things simple. I only bring out the heavy type declarations on special occasions, when they provide a big payoff. Otherwise I keep things as simple and boring and concrete as I can.
There are a few popular Rust libraries which make very heavy use of generics. I find that this sometimes backfires, making those libraries slower to compile and harder for me to understand.
- Rust is naturally pretty fast, on the rough order of C++ in many cases. Even unoptimized programs tend to be very snappy (as long as you remember to compile in release mode and buffer I/O).
- Rust tends to warn me if I make a dumb mistake, rather than having my program mysteriously corrupt memory at run-time.
- Rust has very nice support for portable CLI applications, both in the standard library and in third-party libraries.
- With a bit of extra work, I can usually deliver a single, statically-linked Linux binary. Go is even better at this, but Rust does it well.
- It turns out the "algebraic data types", what Rust calls "enum" and "match", are just a really nice way to write down every possible "case" or "state" of some value. If a piece of software involves hundreds of special cases, Rust allows me to think about them clearly.
- Rust has surprisingly good third-party libraries for many things. (Not everything.)
- Multithreaded Rust apps are a dream.
Cons:
- Rust forces me to keep track of whether things live on the heap or the stack, whether I'm passing them by value or reference, and so on. This is good when I want performance, but for other kinds of code, it's just a "cognitive tax."
- Rust's learning curve is higher than Go, but arguably lower than C++. This is especially true if you've either never worked with stack/heap/pointers before.
- Rust tends to favor "mostly functional" architectures over "mutable objet soup" architectures. This pushes you to use less-familiar designs for games and GUI libraries, for example.