Rust review: Closing thoughts

Thought that the Rust review was over? Think again; I was just on vacation! I’m back now to conclude the series with a bunch of random thoughts and a surprise follow-up post.

The series is coming to an end. It’s time to summarize everything we have discussed so far and to cover a few more items that didn’t really deserve full posts of their own. Most of these miscellaneous items were thoughts that I jotted down while reading TRPL book. I will follow the same good/bad/ugly structure I used for the Go review a couple of years ago.

The neutral

Rust’s default coding style is something that aligns closely with my personal preferences. The style doesn’t matter at all when using a language, but I’ve always preferred 4-space indents and snake_case over smaller/larger indents and CamelCase. Rust matches those preferences well.

Rust was originally created by Graydon Hoare, which incidentally is the same person that created Monotone over a decade ago. Monotone is one of those few projects that left me in awe when I discovered it and Rust has done a similar thing. Keep an eye on this person.

The good

Immutability by default, which allows the programmer to clearly declare intent and, in the case of Rust, allows the compiler to perform complex sanity-checks.

The match keyword, which provides a mechanism to easily unpack complex types and to better implement conditional execution paths.

The ecosystem, which cleanly integrates with other common tools and editors. This includes the use of semantic versioning and plugins for Visual Studio Code and IntelliJ.

No implicit type promotions, not even to booleans in conditionals. let foo = 0; if !foo { ... } is just invalid because a negated integer is not a boolean. Pet peeve of mine while reading C code and where everyone likes to treat integers and pointers as boolean.

Lack of a null value. This ensures that all objects are always valid. There are cases where you need objects to exist conditionally, and in those cases you can wrap them with an Option type (which exists in plenty other languages, including Java, Haskell, and in C++’s Boost). The language does not let you express accesses to those objects without a previous check for their validity, which is good because the cost of the check and the possibility of “nullness” becomes obvious and can be assessed.

Generics! OMG, they can exist without wreaking havoc! (Compare to Go.) They work as you may expect—that is, they work just fine.

Imports must be explicitly specified for any symbol external to the current file, which gives clarity as to where they come from. This is useful in understanding the dependencies a piece of code has, as they are always visible in the code. Compare this to transitive includes of header files in C/C++, which pull in symbols you are not aware of.

The bad

I don’t have much to say here, but this may just be because I haven’t used the language enough in a real-world project to notice the real problems Rust may have. It’s obvious that Rust still has room for improvement based on the many open RFCs. I have one thing to say though:

Learning curve. While not a major problem in my opinion, it’s something that bites users so it deserves being listed as a downside.

The ugly

The book, which is well-organized but would benefit from a good editor. (This comment might be stale as the book has seen a lot of work since I read it two months ago and is now available in printed form.)

Trait usage is not directly connected to imports. In other words: using a trait may require importing a module, but the trait usage and the module have zero visual connection. This makes understanding where things come from a bit more difficult that it could be, but at least the compiler sanity-checks missing and unused trait imports.

The syntax is cryptic to any newbie, especially due to lifetime annotations like <'a>, and the ? error handling operator. Easy to get used to these after reading the documentation, but any newcomer will feel the language is alien. Compare this to Go, which is a language you can read without much training.

Shadowing of variables feels dangerous as it obscures the principle of single assignment. In practice, this has proven to be necessary when writing certain kinds of constructs to keep the code clearer than it would otherwise be.

Weird one-off syntax constructs that feel like after-thoughts. Some examples are the move keyword or the Sized trait. Just my feeling though; maybe their design is intentional and well-justified.

Did you like sendmail.cf? Because Rust’s macros are how you get sendmail.cf again. OMG what an awful thing. Fortunately it seems that macros are still subject to change so there is hope that they’ll evolve into a more manageable animal. Won’t cover anything else on this subject as I haven’t had the “fortune” of writing any.

Strings. Ah, strings, a difficult topic. Rust has chosen to have many different string variants and reference types. The common String and &str will suffice for most uses… but as soon as you try to interact with the underlying operating system, things becomes messy because the OS primitives might not care about UTF-8 (which is what the mentioned types expect). This is not a bad thing at all—being aware of encodings is a must for proper operation under international environments—so this aspect just reflects the ugliness of the real world.

Conclusion

Rust is a very promising language, and its momentum might be what’s necessary to eventually displace C++. For a language that saw a lot of churn before its 1.0 release and that was only stabilized three years ago, the language is very decent. Be sure to check it out. The concepts you learn along the way will be applicable to other languages.

And that’s all folks. I hope to post more Rust-related articles, but the review series concludes here.