Hoverbear writes about Rust, PostgreSQL, and NixOS, including state machine patterns, Raft, and Rust on the web.
HN commenters cite its Rust state machine pattern post, with one noting its enum approach avoids dynamic dispatch and makes state data easier to reach.
That's a legitimate criticism. As of yesterday, how to convert "String" to "&str" still wasn't nailed down. (".as_string(), or "&[]"?). Things are far better than they were a month ago, though.
After using Rust a little, I get the feeling that the borrow system, the big innovation, is in good shape. The type system is clunky. Take a look at this date/time module:
https://github.com/lifthrasiir/rust-chrono/blob/master/src/datetime.rs
Note that none of this code does any computation. It's all field access and update. It's not a multi-calendar system with time zone tables. This is the code for stuffing the year into a Datetime object:
#[inline]
fn with_year(&self, year: i32) -> Option<DateTime<Off>> {
self.local().with_year(year).and_then(|datetime|
self.offset.from_local_datetime(&datetime).single())
}
This has generics and a closure, neither of which ought to be necessary. Despite all this generality, there are two copies of this code, one for time-zone aware and one for "naive" timestamps. The author of that module has misgivings about the structure. He writes "This is a bit verbose due to Rust's lack of function and method overloading, but in turn we get a rich combination of initialization methods." This is the best of the three date-time modules available; the other two won't even compile with the current build system and are abandoned.
There are some neat ideas in the type system. One is that if you have something which might not be…
The borrow checker isn't nearly as big a deal as you make it out to be from a complexity perspective. You have to take some time to learn the paradigm but when you do it's easy. The challenge is people assume it writes like C because it looks like C, and are frustrated when it doesn't. After a couple of months, you'll realize the borrow checker is your friend, you just need to feed it stuff in a way it understands.
The borrow checker is great for all sorts of things, like ensuring that you only have a single mutable reference or an arbitrary number of immutable references to objects (to prevent corruption) and for the pseudo-threaded model interrupts entail. Memory safety matters in embedded systems, too. You can encode state cleanly with ADT enums [2] where the language can statically detect invalid state transitions.
It's also a much more expressive language, providing you with great abstractions with no added cost. The ergonomics are pretty great compared to dealing with C once your peripherals / register accesses are wrapped, as often are already. For instance, check out the stm32 package [1].
Being able to rely on the compiler more means you get correct code faster, and debugging on embedded systems can be absolute hell. Anything that helps me avoid it is a massive win in my books.
[1] https://github.com/stm32-rs/stm32-rs
[2] https://hoverbear.org/2016/10/12/rust-state-machine-pattern/
Please don't let my negative experience put you off learning Rust -- its an important language and worth putting the effort in. I would recommend diving in and seeing if it works for you.
For informational purposes, this is what I still find difficult:
- The module system. It constantly catches me out.
- I think that I understand traits but struggle using libraries that have a lot of traits in the public interface.
- Error handling is nice but there are error handling libraries going in and out of fashion every few months. Which one do I use?
- Callbacks combined with the borrow checker.
- Complexity of idiomatic code such as state machines [1].
- The strict string handling. What about strings that should be UTF-8 but come over a network protocol?
- Combining async code with non-async code. Also, how do you write a library that does I/O and supports both sync and async?
- Iterators. I have some functional programming experience but I hit problems with iterators combined with the borrow checker.
[1] This example has just 3 states and is much simpler than most real world examples: https://hoverbear.org/blog/rust-state-machine-pattern/
There are ways to prevent some logic/protocol problems at the type level using rust:
https://hoverbear.org/2016/10/12/rust-state-machine-pattern/
https://blog.tarn-vedra.de/posts/phantom-types-for-safe-apis.html
https://insanitybit.github.io/2016/05/30/beyond-memory-safety-with-types
https://scribbles.pascalhertleif.de/elegant-apis-in-rust.html#session-types
Some libraries use such features to prevent certain kinds of nonce-reuse during encryption (one of the more common ways of screwing up encryption). These constraints are checked at compile-time.
Such guarantees are not possible with weaker type systems like that of C.
To me, the power of it isn't just in dynamic memory but also in that Rust has clear move vs ref vs bit copy vs logical copy (clone) and use-after-free protection.
As one example of the benefit of this. This makes me feel a lot more comfortable writing state machines in Rust's type system.
See https://hoverbear.org/2016/10/12/rust-state-machine-pattern/
It's to create random numbers to make the star field on the home page:
https://hoverbear.org/
It's defined in this file:
https://github.com/Hoverbear/hoverbear.org/blob/root/sass/_magic.scss
Which links to an explanation here:
https://medium.com/hypersphere-codes/counting-in-css-unlock-magic-of-css-variables-8e610881097a