Enforcing style in CI for Rust Projects
It’s a common trope that developers love to argue about code style. From the age old JavaScript debate over semi-colons, to even older debates over “tabs vs spaces”, there’s been a lot of emotional energy spent on this topic.
This article isn’t a new addition to that debate. Instead, this article will attempt to help you avoid the debate entirely, at least in your Rust projects, by explaining how you can use the rustfmt
tool to enforce style guidelines using CI. We’ll start with a brief introduction to rustfmt
, so feel free to skip the next section if you are already familiar.
Hello rustfmt!
rustfmt
is a tool for Rust projects that helps enforce style. It runs a set of lints against your code and will fail if any of the checks don’t pass.
To use rustfmt
you first must add the component, via rustup
:
rustup component add rustfmt-preview
Then, to run it:
cargo fmt
It’s worth noting that when you run cargo fmt
it will change your code formatting. It will only fail if it cannot safely change your code. It’s quite smart though, and generally can take care of most of the problems for you, leaving just a few for you to solve on your own.
You can configure the default lints if you would like. For more on that, read this. Generally speaking, I don’t recommend configuring a tool like this, but I know that some folks really like to.
The rest of this article will discuss using rustfmt
in CI. We’ll use Travis CI for our examples, but you can use whichever CI provider you’d like without any significant edits.
Stable or Nightly
When testing on CI, one of the first decisions you need to make is which versions of Rust to test on. You’ll need to pick which channel is best to test your code on- and this is likely enforced by the dependencies your project uses.
Now that rustfmt
is in the mix, there’s a few more things to consider. As it currently stands, rustfmt
is slightly different on stable
vs. nightly
channels. If you are able to pick just one channel, the examples below should be very useful for you. We’ll discuss how to handle projects with multi-channel support in the next section.
Stable: use write-mode diff
On stable, your .travis.yml
should use--write-mode diff
. This feature will be deprecated in favor of --check
, which is currently on beta
and will land in the next release in early August (~1 month).
Nightly: use check
On nightly, your .travis.yml
, should use --check
. Long-term, this is the strategy you should use, and it should be stabilized in a little less than a month.
Style across Nightly and Stable
Many projects, especially libraries, support both nightly and stable channels. This poses a unique problem for testing style because rustfmt
has different checks in each channel’s version. This is because rustfmt
is still in preview mode.
As a result, it’s best to pick one channel to enforce style.
Nightly style, stable code features
If you so desire, you can use rustfmt
on the nightly channel, even if you only want to test your project on stable. It’s worth noting that you can’t do the inverse.
Here’s how:
You’ll note that the Rust channel is set to stable
but we pass --toolchain nightly
when we add the rustfmt-preview
component, and then also use +nightly
when we call cargo fmt
.
Communicate to your contributors
When deciding which channel to pick, you should pay attention to the channel your project’s contributors generally work on. I would recommend adding a section in your project’s CONTRIBUTING.md
explains the need to choose one to enforce on. You can use this as a starting point (pulled from the wasm-pack
CONTRIBUTING.md
):
You can also use a GitHub Pull Request template to help communicate this requirement to your users, for example (pull from the wasm-pack
PR template):
A Moving Target
As rustfmt
works towards stabilization, another thing to be aware of is that the style checks in both stable and nightly will change. This can definitely be frustrating!
Updating rustfmt
If you are running into an issue with a contributor where style checks pass locally, but not on CI, it’s likely that the contributor has an older version of rustfmt
. To update, run:
rustup update
Participate in the Discussion
If you are curious about what is in store for when rustfmt
is stablized, or just want to see what lints might change, you should check out the following links:
And there you have it! Some best practices on using rustfmt
to enforce style on your Rust projects in CI. Perhaps one day the whole developer community can relish, as I do, in the relief of leaving style enforcement energy to computers.