When Style is Standardized, Style Can Be Standard
The rust-lang-nursery GitHub organization is a fantastic group of folks building tools for both working in, and working with, the Rust language. One of these tools, rustfmt, is quite helpful for maintaining a consistent code style throughout a project.
rustfmt is pretty easy to work with. Its default behavior, when executed on
a file, is to check it for anything that violates the style guide, and if
anything needs to be changed, backup the file with a
.bk suffix and silently
replace the original with the new formatting. If passed a valid rust file, it
returns 0 regardless of any changes being made.
rustfmt only operates on a
single file, so if the current directory is a
cargo project, a handy
fmt wrapper is provided that checks the entire project.
There are a few nice ways to integrate
rustfmt into your workflow, such as
and git commit hooks. Unfortunately, these depend on the individual developer
to use them, and thus do not enforce any project-wide policy.
A lot of the themes brought up in the previous paragraph are pretty reminiscent of testing: consistent when used, but largely reliant on the developer to use them. Since Travis-CI was already set up to run our tests and report upon failure, why not leverage that build process to handle formatting?
The default Rust
.travis.yml provided by Travis CI is pretty straightforward:
It relies on
cargo to run
cargo build --verbose and
--verbose during the implicit “script” phase of the build.
With just a few slight modifications, we can have format checking be a part of
Installing the Tool
Travis CI can has an optional “install” step (see Travis-CI: The Build
where we can have
rustfmt and then add it to our path. This step can take a very long time to finish due to compilation of the
env_logger crate. Thankfully Travis CI provides a mechanism for caching dependencies with the
cache key. Putting what we have so far together, we get:
Recall how I said that any non-zero return status breaks the build? Well the
cargo install command returns non-zero if the desired package is already
installed (which means caching worked!). In order to get around this,
we massage it into returning true upon “failure” and thus continuing the build.
Checking the Style
Now that our build context has
rustfmt, we can have it run along with the
builds and test. It would be nice if we could have Travis-CI automatically and politely run
rustfmt and fix any format-offensive code, but that would have to rewrite git history and is out of the question. The best we can do is break the build upon a push or pull request, so that the developer knows to run the format command and recommit.
Since any command with a non-zero return status breaks the build, we just need
cargo fmt to break at some point.
Again, the default behavior of
rustfmt, even when wrapped
cargo, is to silently backup and reformat and offending code. We need a
way to detect when code is malformatted. If we check the usage with
rustfmt -h, we see the
--write-mode [replace|overwrite|display|diff|coverage|checkstyle] option.
The default setting is ‘replace’, but after trying them out, it seems the most
helpful mode for this purpose is ‘diff’. It provides two things that work well
in this context: a non-zero return code upon code that doesn’t meet the style
guide, and output that makes it clear why the build broke. Since we’re using the project-aware
cargo fmt wrapper, we cannot use
rustfmt flags directly until we insert a
At this point, we could just put the
cargo fmt line in the “install step”,
it would certainly work as desired. However, for the sake of clarity and
further modularity, let’s make the “script” step explicit now. This means
cargo fmt command as well as the default
commands as elements of the “script” key:
And there you have it! I hope this helps you make the world a cleaner, more stylish place! If you have any feedback or questions, feel free to comment below or reach out on twitter.
This work is licensed under a Creative Commons Attribution 4.0 International License.