Rust is one of the most talked about languages in recent years, as the Firefox browser is gradually being rewritten to use it and more and more companies are starting to use it. While its most prominent feature – borrow checker – has been discussed numerous times, there are also many smaller features, which provide a comfortable experience while developing in Rust.
Convenient switching between toolkit versions
One of the most common issues that affect Rust programmers is that the most tempting language features are always available only in nightly. On the other hand, for professional development, it’s a good idea to keep the ecosystem quite stable, with one toolkit version that is expected to make our codebase work, and which will be supported for as long as possible. Is it possible to seamlessly switch between different releases of the Rust toolkit?
Enter rustup – the Rust toolchain installer. It’s the tool that allows not only for installing, but also updating and switching between different toolchains.
The main rustup website is rather minimalistic, but anyone in need of more information can find it in a quite extensive rustup project’s README.
Module system
Project’s source code rarely can be contained in a single source file. Different programming languages have different approaches to solving this problem – one of the most known in the embedded programming world is the C/C++ way – #include, which essentially causes the referenced file to be directly pasted in its entirety by the preprocessor.
This solution is simple to implement, easy to understand and usually works. There are pretty severe issues with this approach though: any changes made in one file can spill into all others which include it – directly or indirectly. It can be a rogue #define which changes the behaviour of the code or using a namespace directive.
There’s also a risk of including one file more than once, that would cause things to be defined twice – and every C/C++ developer has the include guard already in their muscle memory or editor templates. Rust solves the problem differently – by using a module system.
Modules in Rust can work inside of one file in a way similar to namespaces in C++, with the additional possibility of default privacy of module contents – anything that needs to be visible outside of the module needs an extra pub keyword.
But when a module itself is prepended with a pub, then it can be used in other source files, while being completely independent of them. More information can be found in The module System to Control Scope and Privacy chapter of the Rust Book.
Crates
Almost all software developed stands today on the shoulders of giants by using at least one third party library. Most modern programming languages provide an easy way for the code to depend on a given version third-party library, which can be automatically downloaded and built by the build system. Rust isn’t different in this matter – cargo, build system used by Rust, gets the current project information from Cargo.toml file, where dependencies on other libraries (called crates) and their versions can be also stored. Then, during the build process, crates are downloaded and built.
Crates can be browsed using Rust Package Registry, which also conveniently provides a snippet that can be pasted into the Cargo.toml to use the chosen crate.
As an interesting side note, cargo and crates are both references to Cargo Cult. Knowing current development practices of stitching libraries together until it works, it’s a pretty humorous touch.
Integrated testing
Software testing is one of the basic elements of modern code hygiene. There are testing frameworks for almost all programming languages, but Rust decided to be a step ahead and a simple test framework is provided with the language itself. Citing a simple example that can be found in the Writing tests chapter of the Rust Book:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
In this example, module tests will be available only during the test execution, and the it_works function is marked as a test, therefore it will be run when the cargo test command is invoked.
But that’s not all – it’s possible not only to test the correctness of our program, but also to benchmark it. To do that, it’s needed to create a function marked as bench, taking a Bencher object, which will do the measurement. Thanks to that initialization of objects can be done outside of the measured code fragment. At the time of writing this text, benchmarking is an unstable feature and needs to be enabled using the test flag. More information can be found in the test library features in the Unstable book.
Documentation tests
Rust also provides rustdoc, which is an automatic documentation generation system, similar to Doxygen or Sphinx. But one of the most common problems when documenting the rapidly moving codebases is to keep the documentation up to date. Rust tackles the issue by allowing one to include code samples with assertions, which can be easily compiled and evaluated. More information can be found in the rustdoc documentation.
Summary
Rust provides not only a lot of nice programming constructs, but also a really comfortable ecosystem that aims at solving practical problems that programmers have in their daily jobs; therefore, it shapes up to be not only an academic-only curiosity, but a tool that could be used in long running commercial projects. If you haven’t played with it, then try it out – Rust is not only a useful tool, but it’s also lots of fun.