Rust...

I've been dinking around with Rust on a semi-serious basis for the past several months. I won't say that I've had enough time to form a fully educated opinion, but I do feel like I've gotten the general shape of the language... certainly enough to have at least a preliminary opinion. So... here goes nothing.

Background

I come from the school of thought in system-building where I trust the compiler wholeheartedly. Refactoring a large codebase is far more pleasant in Scala than in Python... and the latter becomes far more pleasant with type annotations. I appreciate it when the compiler yells at me for changing a reference but not the referant. I'm willing to tolerate the compiler's pedantry, because ultimately it pays off in the long run.

In short, I'm the type of person Rust is aimed at.

The Good

I've honestly had a ton of fun writing Rust. Not quite Ruby (or Frontier) levels of fun, but it's a very snappy language with good developer tooling. Rust's Struct/Trait model is, I think, much cleaner than normal object inheritance for most things that I typically use a class hierarchy for. And the borrow checker does a really good job of forcing me to think through memory management issues that I'd normally wave off.

Developer Tooling

Rust's developer tooling is flat out the cleanest that I've seen in a long time. The rust compiler does an excellent job of providing context for error messages. There's a lot of 'did you mean...' types of suggestions, and those are directly integrated into the LSP. Now, getting the LSP properly configured with sublime took a bit of time/effort, but once I did, everything just works: Easy access to docs, to suggestion implementations, and to error messages. Rust is incredibly pedantic about code artifacts, but the tooling does a lot to prevent that pedantry from becoming a barrier to authoring those artifacts.

Compile Times

My last major project relied on SBT. 'nuff said.

Documentation

cargo doc is up there with JavaDoc and ScalaDoc. Plus, cargo doctest's ability to keep code snippets up-to-date, and the fact that cargo doc synthesizes documentation for all dependencies, makes the documentation ecosystem pretty nice.

The Bad

Rust is Judgy

Most languages optimize for the typical use case. For example, managed languages like Java just put everything on the heap, and the user doesn't have to think about whether the size of an object can be predicted at compile time. Rust, meanwhile, puts that flexibility behind an explicit Box type (resp., Rc, Arc, etc...).

In short, Rust, the language, sits there, judgmentally eying you every time you try to do something that would just be the default in any other language.

It took a while to recognize this fact, and just start using String, Box, and the like without worrying about whether there's a 'cleaner' solution that can avoid them.

Compiler Magic is the Default

A lot of idiomatic Rust leverages non-obvious compiler trickery. As a simple example, let's say you want to map a function that can return a Result. If the map returns an Err on any of the inputs, we'd like to return an Err from the enclosing function, something that would normally be possible with the ? operator.

The fact that this doesn't work makes sense when you realize that Iterator is lazy. However, it turns out that there is an idiomatic way to implement the same idea: let foo: Iterator<Result<T>> = ... let bar: Vec<T> = foo.collect()? The collect method leverages Rust's compositional From/FromIter traits to factor the Result out of the collection when generating it.

All of the individual pieces are documented. The rules by which they are composed are obvious. However, the fact that this is the idiomatic way to get a Result out of a map is not at all obvious from any of the simple documentation.

In other languages (e.g., Scala, Ruby), there's a bevy of methods covering various specific, common use cases. These are far less flexible than collect(), but are far more discoverable.


This page last updated 2024-05-06 11:22:16 -0400