A Parenthesis Clippy Forgot — My First Contribution to Rust
I just finished Rust for Rustaceans. Last book on my Rust roadmap. In the final chapter, Jon Gjengset pushes you to go contribute to the ecosystem — not as homework, just as the natural next step once you've read enough to understand what you're looking at.
So I went looking. Found a good first issue on Clippy, Rust's official linter — which means basically every Rust project runs it.
The reported bug: double_comparisons is one of those lints that offers a machine-applicable fix — run cargo clippy --fix and it rewrites your code for you. It correctly spots that left < right || left == right collapses to left <= right. The problem, as reported, showed up in one specific shape:
fn reject(left: f32, right: f32) -> bool {
!(left < right || left == right)
}The suggested rewrite dropped the parentheses around the whole thing. Instead of !(left <= right), you'd get !left <= right — which doesn't even compile for floats (E0600, you can't negate an f32). cargo clippy --fix detects that, silently rolls the fix back, and still exits 0. No error in your terminal. In CI, you'd never know a fix was attempted and failed.
That's the bug as reported. I started digging into the root cause, and that's when it stopped being about floats.
! binds tighter than <= in Rust. So !left <= right, without the parens, always parses as (!left) <= right — not !(left <= right). For f32 that's a type error, so you get caught. But plenty of types implement Not — plain integers included:
fn exceeds_limit(balance: i32, limit: i32) -> bool {
!(balance < limit || balance == limit)
}exceeds_limit(100, 100) should be false — sitting exactly at the limit isn't exceeding it. The broken suggestion rewrites the body to !balance <= limit, no parens, which parses as (!balance) <= limit. For balance = 100, !100 is -101. -101 <= 100 is true. Now the check says you've exceeded your limit when you're exactly at it — a declined transaction, a false fraud flag, whatever this feeds into. Same inputs, opposite answer, and it compiles clean.
No compile error. No rollback. cargo clippy --fix would apply this, call it a success, and your program would silently do the opposite of what it did before. That's strictly worse than the reported case — at least a failed compile tells you something's wrong.
The fix itself turned out to be type-agnostic. It just checks whether the snippet being replaced is already wrapped in balanced parentheses — pure text, no type info involved — and re-wraps the suggestion if so:
let span_snippet = snippet_with_applicability(cx, span, "", &mut applicability);
let sugg = if has_enclosing_paren(&span_snippet) {
format!("({sugg})")
} else {
sugg
};So the patch that's merged already protects bool and integers too, not just f32 — I checked has_enclosing_paren's source to be sure it wasn't quietly special-cased. It isn't.
What stuck with me wasn't the fix. It was realizing the issue report and the actual bug weren't the same size. The report was "this one case doesn't compile." The bug was "this suggestion doesn't know what it's wrapped in," and the float case just happened to be the one shape that errors loudly enough to get noticed and filed. Everywhere else, it would have just been wrong, quietly — and the only thing standing between you and that is whether your own tests actually cover the case Clippy just "fixed" for you.