I understand that clippy and rustc are inextricably linked so I imagine the answer to my question is probably "no", but is there any way to pin to a particular version of clippy and upgrade the entire toolchain?
We currently run our PR and CI pipelines on our MSRV, latest stable, and latest nightly. But the problem with the last two is that, often times, clippy adds new stylistic lints that now only start "randomly" breaking our builds (today, 1.90 just came out so our "latest" changed) and, in this case, it's a new lint and we're a workspace of lib crates, so we have to add something like:
And I call clippy::manual_is_multiple_of "stylistic" because there's nothing wrong with doing it manually. Seems several new lints in the past couple releases have been this way: a method is added to simplify(?) code but doesn't obviate the old code. But these lints aren't categorized in clippy::style so we can't just blanket ignore them in our workspace Cargo.toml. Every month when a new toolchain comes out, we inevitably have to create a one-off PR to fix new lints that, most often, aren't actually a problem.
There’s no way to ask rustup to mix tools from different toolchain versions. However, you can have your CI run cargo +1.89.0 clippy instead of cargo clippy.
I also recommend that you break out linting into a separate CI job that succeeds/fails independently of the build per se, so that you can get more information out of failures and so that you can test the build even if there were warnings and -Dwarnings.
TBH, for things like style I'd say to just stop making them break the build, especially if you're going to try to run them on lots of versions.
https://rust-lang.github.io/rust-clippy/master/#collapsible_else_if is just not that important, for example. They're warnings, not errors, for a reason. Show people a hint in their IDE? Sure. But insist on every field shorthand being used if possible? I don't think it's worth it.
That said, one classic approach is to just have a file that has a number of warnings in it, so you can have the warnings but not allow more than that number of warnings in the build. Then as you fix the warnings you decrease the number, keeping people from adding new instances of the warnings, and eventually getting down to zero on the new toolchain.
Alternatively, don't rely on lint groups. Pinning a specific version of Clippy is not much different than defining [lints.clippy] to contain all the individual warnings/errors. This is not entirely true of course since new versions of Clippy can address false negatives; thus you may still get a warning/error that you didn't before but presumably that's a good thing.
We use default clippy settings plus a couple exceptions. We don't lint on groups. I'm saying that manual_is_multiple_of is stylistic, even though it's not categorized as such. And that's the problem: someone somewhere opines that "everyone should use this new function" and makes it a non-stylistic lint that we could otherwise ignore en masse. Instead, it's lints like this that keep breaking our builds whenever a new release comes out.
And who doesn't do release testing or publishing with -Dwarnings? Most major projects do that I've seen. Warnings-as-errors is common in a lot of languages. All our languages in our org do it, ignoring very specific instances of lint as needed.
And clippy is already part of an "analyze" job, but that still fails the build because no one looks at warnings if they don't break the build, hence turning on warnings-as-errors in all our language repos.
New .NET lints, for example, are typically opt in unless they are for issues that should really be considered (warnings) or serious issues that should be fixed but otherwise compile (errors). Why is using function Y over expression X even a warning? Is expression X wrong? Certainly not in this case.
So in addition to defining the lints you do you want to deny/warn, you need to allow all groups since unlike rustc, all Clippy lints are part of a group. Something like:
And this is horrible, because it de facto means that you get almost no useful warnings from the C# compiler, not even obviously-worth-breaking-things ones like "hey, did you know your function just calls itself immediately?".
My job for many years was primarily C# (with C/C++ and some other languages as needed), and now it's working in Rust. Not being broken by ~monthly tool updates was much better. We did stay on top of new, interesting lints and added them as needed. But most of those lints weren't stylistic like "use this new method instead of an expression you had to use before". And that's my biggest gripe here: stylistic lints shouldn't be mandated even as warnings because so many repos turn warnings into errors to make sure they are seen.
They're not "mandated" in the slightest. You both opted into running a tool that's not the compiler and opted into making those warnings break your build.
This is why I usually run Clippy in a separate CI job with pinned (stable) toolchain version. The version gets updated manually from time to time together with fixes guided by new lints.
In an individual crate, you can make a top-level file .clippy.toml and put in the clippy version you want, like:
msrv = "1.75.0"
I wonder if something like this might also work if you put the toml file in you $HOME/.cargo/ directory (or the proper location for your OS). That might set it for your whole devenv. I haven't tried it.
I've done this for embedded systems where we're locked into an old compiler for the production builds, but typically use the latest stable for development. You just don't want the latest clippy telling you to do things that the old compiler doesn't support.
Not really answering your question, but trying to get at the underlying issue. While Clippy's lints can sometimes be annoying, they're helpful far more often than not. I don't think pinning Clippy to an (increasingly) obsolete version is the right way to go. Nor is maintaining a (probably ever-growing) list of allow lints (expect would be slightly better, but not by much). It's just setting yourself up for more headaches in the future.
If it were me, I think I'd take Clippy's advice and just use is_multiple_of. While you may or may not prefer it stylistically, it seems (if I'm reading the docs right) to be not just a matter of style: is_multiple_of really is better because, unlike %, it doesn't panic if the divisor is zero. (Maybe in your program the divisor never can be zero, but things can never happen have a funny habit of happening all the same.) I'll sacrifice any amount of style to make my Rust programs less panicky.
Anyway, TIL about is_multiple_of and I'll get into the habit of using it instead of %, thanks!
That's not an option for lib crates supporting an older MSRV. The concerns of lib crates are far more numbered than bin crates e.g., also supporting any async executor a bin crate using your lib might want to use. We test against our MSRV (for us, no less than 6 months old but only changed if necessary), latest stable, and latest nightly.
That's why we had to attribute lines with #[allow(unknown_lints, clippy::manual_is_multiple_of)] because, otherwise, anything older than 1.8x (forget which x exactly) then fails on the otherwise unknown lint.
I don't disagree with lints generally being helpful when they actually discover a bad pattern, but how is "instead of expression, call this new function" in line with that mentality? It's not like we get some perf benefit - maybe even immeasurably worse if not inlined - so why isn't that not either a pedantic or stylist lint? That's my issue here. We've been bitten by several such lints in the past year that, in our opinion, are merely stylistic.
If you set package.rust-version in Cargo.toml, or configure clippy's msrv setting, then Clippy will not recommend anything which would fail to compile on that Rust version. It’s set-and-forget until you decide to increase your library’s MSRV.
If you have declared rust-version and clippy is suggesting changes that would not work on that version, that’s a Clippy bug that should be reported. That’s the case I was discussing. You will still get new lints that are not about using new features and should be addressable without touching MSRV.
It is not sufficient to just set your lints and expect that to be stable. Lints change over time. I have all my clippy CI jobs pin to "stable" with renovatebot updating it every release. Just with 1.91, I had maybe 10 repos I had to fix so the clippy update PRs could be merged.
I don't know know what you mean. The only differences between "pinning" a specific version of Clippy and manually specifying each lint are false positives/negatives being fixed and new deny-by-default lints. That's what I was saying.