Why Rust?
- It’s fast (runtime performance)
- It’s small (binary size)
- It’s safe (no memory leaks)
- It’s modern (build system, language features, etc)
When Is It Worth It?
- Embedded systems (where it is implied that interpreted languages are unsuitable)
- Safety-critical / security systems
- Anywhere that runtime performance is a significant measure in the overall cost of the application
What Else Do I Know About Rust?
The Development Tools Are a Joy
I know a thing or two about the modern development environment for C and C++ and, though it’s better than it was a decade ago, it’s still a lot of moving pieces to get right. Rust fits in as a fantastically modern replacement for the C/C++ ecosystem.
Rust implies Cargo and Rustup, and this results in a powerful yet simple combination of tools that does everything you’d want from a modern software project. Platform engineering for projects written in Rust is as good as it gets – possibly the best I’ve ever worked with. You’ll get a fast and easy-to-use dependency manager, a brain-dead simple build system, linter, formatter, etc. Use JetBrains CLion as your Rust IDE, and you’ll be off the races in no time.
The Language is Difficult, But Not Impossible
I also know that software written in Rust is more difficult to write than the same software in Python or Java. Think long and hard about whether or not Rust’s benefits outweigh the increased cost of development for your use case.
Rust has two key “features” that make development significantly different and more time consuming than modern high-level languages:
- Rather than exceptions, Rust relies on return values as error codes, similar to C. The use of Rust’s
std::result::Result
type makes this less painful than C, but still a far stretch from exceptions. - Rust’s memory is managed entirely by the compiler, with deallocation calls being inserted automatically by the compiler where necessary. There is also no garbage collector, and the developer does not use a
delete
keyword or function.
In practice, both of these changes are quick to adapt to for any competent C/C++ developer, and in the right environment, they make a lot of sense. The ?
syntax along with std::err::Result::map_err()
make it easier to handle return types of std::err::Result
than you might imagine and the error messages provided by the compiler’s “borrow checker” are nothing short of stunning. Really. GCC maintainers, I hope you’re paying attention.
Adapting to the memory model will result in some developer frustration, and dancing around the plethora of std::err::Result
objects will result in a few more lines of code than your Python app might have (I found the count to be about double that of Python), but these are surmountable and, for the right project, easy trade-offs.
Adapting to this new language brings a learning curve with it, but if you can learn Vim, you can learn Rust.
Stacktraces Are Not Built-In
Like C, and even C++ exceptions, stacktraces are not built into the language. The std::err::Result
object is equivalent to a union of two types: T
, and E
. T
represents your successful result and E
represents your error value. There are no bounds on E
, and there is no magic appended to E
. If you’d like your error to be represented as an integer (u32
for instance), there’s nothing stopping you from doing that. And of course there is no stacktrace inherent to the integer types. If a method, foo()
, returns Err(1234)
(this means an instance of std::err::Result<T, u32>
, where the result is an error equal to 1234
), you do not know if the foo()
itself returned Err(1234)
or if some function further down the stack returned the error and foo()
merely allowed the error to bubble.
There appear to be some crates (libraries) to help with this if desired, but just understand that stacktraces are not built into the language like you get with Java and Python exceptions. It’s up to you, the developer, to ensure that your error messages and log lines include any and all relevant data to help debug errors after the fact.
Libraries Are Adequate
As a strong believer in the Java Spring ecosystem, especially Spring Boot, I’m not sure I’ll ever be convinced than another language is better than Java as the default/go-to for your standard RESTful web service. The ease of use, plethora of documentation and sample code, and large community make writing applications with Java and Spring about as easy as it gets. Rust hasn’t been around long enough for anything close to Spring to crop up.
But Rust has been around long enough with competent and enthusiastic enough developers that what libraries do exist do a fine job of accomplishing your goals. In an effort to learn the language, I wrote a web service that is capable of relaying the contents of a directory to a web browser and allowing those contents to be downloaded. Some of the key crates used include:
- A web framework: rocket
- Automatic JSON serialization/deserialization: rocket_contrib + serde + serde_json
- Logging: log + simple_logger
- CLI argument parsing: structopt
- ZIP file writer: zip
Rocket has been the most surprising during the development phase. Its use of macros on top of “route” functions feel eerily similar to Spring’s @GetMapping
and friends, making for a near seamless transition to a brand new web framework.
As of the time of writing this article, there is no clear answer to “where’s my dependency injection and inversion of control container crate?” I also don’t have a clear answer as to why no such crate exists, but it may be that the intersection of “projects that need the speed and safety of Rust” and “projects complex enough to need a DI and IoC framework” is still slim enough as to not warrant the development effort.
Where Do We Go From Here?
My thoughts can be summarized quite simply:
I am thrilled that Rust exists!
I’ve no intention of using Rust on all my personnel projects, nor do I intend to actively push a client to use Rust on the majority of their projects.
But when I do see a project that needs to be fast – anywhere that C/C++ might have previously been used – I’ll be thrilled to jump on the Rust bandwagon and utilize its modern tooling and high-level language features.