Why does Rust need a linter if the compiler is so smart?
The Rust compiler is the strictest gatekeeper I've ever written code for. It checks my types. It tracks who owns every piece of memory and for how long. It rejects entire categories of bugs before my program has run even once. When something finally compiles, it feels notarized.
So the first time someone told me to also install Clippy, the official Rust linter, my honest reaction was: why? I just got through the most thorough security check in the industry. What could possibly be left? It felt like a club hiring a second bouncer to stand directly behind the first bouncer.
Turns out the second bouncer is checking for something completely different. And understanding the difference taught me more about how compilers work than the compiler ever did.
What the compiler actually promises
Strip away the mystique and the checking half of a compiler answers exactly one question: is this a valid program according to the rules of the language? Does every name refer to something that exists? Do the types line up? In Rust's case, does every borrow follow the ownership rules? If the answer is yes, your program is legal, and the compiler's job is to translate it faithfully. That's the whole contract. The compiler is a judge of legality, and nothing else.
And legal code can absolutely be bad code. Here's the example that made it click for me. This compiles without a whisper of complaint:
if is_ready == true { start(); }
Nothing about it is invalid. But is_ready is already a boolean. Comparing it to true is asking "is this true thing true," which produces the exact same boolean you started with. Run Clippy and it flags the line immediately and tells you to just write if is_ready. The compiler had no grounds to complain, because no rule was broken. It's just the kind of code that makes a senior engineer squint at you.
Same story with if name.len() == 0. Legal, works, always has. Clippy suggests name.is_empty() instead, because it says what you actually mean. Or writing while true for an infinite loop, which Clippy will tell you to replace with Rust's dedicated loop keyword. None of these are errors. All of them are the small stuff that separates code that runs from code that reads well and stays maintainable.
So that's the split. The compiler asks "is this valid?" The linter asks "this is valid, but is it suspicious, wasteful, or the kind of thing that bites someone in six months?" One enforces the rules of the language. The other enforces thousands of small judgment calls collected from years of people getting burned. A linter is basically institutional memory wearing a terminal interface.
Type safe doesn't mean sensible
This also dissolved my original confusion about type safety. "Rust is type safe" means the compiler guarantees your program can't misuse its values: no treating a string like a number, no using memory after it's freed. That's a guarantee about what your code does. It says absolutely nothing about whether your code is the clean, idiomatic way to do it. is_ready == true is perfectly type safe. Type safe and a little embarrassing. The stricter the compiler, the more it felt like it should cover everything, but rules and taste are just different jobs, and no amount of strictness in one buys you the other.
Okay, but how does a linter read my code?
Once I accepted that linters had a real job, I immediately had a dumber sounding question that turned out to be the better one. How does Clippy actually see is_ready == true sitting in my file? My first guess was pattern matching. Basically regex: search the text for == true, print a warning, collect your paycheck.
It's a fair guess, and it's wrong in a really useful way. Regex reads flat text with zero idea what any of it is. Take let msg = "is_ready == true";. The pattern is right there, but it's inside a string literal. It could just as easily be inside a comment. A text search can't tell code from a comment from a string, because at the text level there is no difference. It's all just characters. And there's a deeper problem: to correctly flag "you're comparing a boolean to true," you need to know that is_ready is a boolean in the first place. That fact isn't written anywhere near that line. It's knowledge about the program, and something has to build that knowledge before anyone can use it.
So here's what Clippy actually does: it rides along with the real Rust compiler. The compiler reads your file and builds a structured, in-memory version of your program: a tree that says "here's a function, containing an if, whose condition compares this variable, which is a bool, against the literal true." Clippy walks that tree. And at that level, the lint isn't clever at all. Find comparison nodes where one side is a boolean literal. Done. A string containing == true can't fool it, because by the time Clippy is looking, that string stopped being loose text long ago. It's a string node, clearly labeled, sitting on a different branch of the tree.
This isn't a Rust quirk, it's how serious linters work everywhere. ESLint doesn't grep your JavaScript, it parses your file into a tree and checks the tree. Some quick and dirty lint tools really are glorified pattern matchers, and the earliest ones started that way, but everything you'd actually trust today works on the tree, for exactly the reasons above.
The question under the question
And that's the part that got me. Clippy sees a tree. The compiler builds that tree. Builds it from what? From my file. Which is text. Just characters in a row.
I had been programming for years assuming, without ever saying it out loud, that the compiler and I look at the same thing when we look at code. We don't. I see variables and types and if statements. The compiler starts from a string, and every single concept I think of as "code" is something it has to reconstruct from raw characters, from scratch, every time it runs. The variables aren't in the file. The file is letters.
How that reconstruction works turned out to be the best part of this whole rabbit hole, and it's where we go next: the compiler sees characters, not code.