Lesson 7.4 · 11 min
Tools that check your code
Chatterbox is nearly 400 lines now. It works and its tests pass, but is it tidy, and is it written the way other Rust programmers would write it? You cannot know every habit yet. Rust ships tools that do: one that lays the code out, one that reads it like an experienced reviewer, and one that lets you look inside a running program.
Adds to Chatterboxclippy and rustfmt tidy the code.
One style that nobody argues about
Code is read far more often than it is written, and much of reading is spotting shapes: where a block starts, which lines belong together. If every person spaces their code differently, every file looks slightly foreign. Worse, when two people share a project, one of them re-spacing a file makes a hundred lines look "changed" when nothing they do has changed.
Rust settles this with rustfmt, a formatter: a program that rewrites the layout of your code, its spaces, line breaks and indentation, into one standard style, and never its meaning. It comes with Rust, and Cargo runs it with cargo fmt. Here is a function written carelessly, then rustfmt's opinion of it. cargo fmt --check only reports what it would change; plain cargo fmt changes it and prints nothing.
$ cargo fmt --check Diff in ~/chatterbox/src/main.rs:158: } /// True when the text is a command: it starts with a slash. -fn is_command( text:&str )->bool{ -text.starts_with( '/' ) } +fn is_command(text: &str) -> bool { + text.starts_with('/') +} /// The first word of the text: a slice (&str) pointing into it, not a copy. fn first_word(text: &str) -> &str { $ cargo fmt $ cargo fmt --check
Lines starting with - are what is there now, lines with + are what rustfmt wants. After cargo fmt, the check is silent: nothing left to change. Every Chatterbox listing in this course has been through rustfmt, which is why they all look alike. Many editors run it each time you save, so you never think about spacing again.
A second reader: clippy
The compiler checks that your program is allowed. It does not tell you when there is a simpler, clearer or more usual way to say the same thing, and as a beginner you cannot know those ways yet. Clippy can. It is another tool that comes with Rust, a collection of several hundred lints: small rules, each one a habit experienced Rust programmers agreed on, like "this if could be shorter" or "this loop already exists as a method". Run it with cargo clippy.
On Chatterbox after lesson 7.3, it has nothing to say. That is good news, but clippy's default set is only the lints almost everyone agrees with. You can ask for more by naming a lint or a group after --, with -W for "warn me about this". One lint worth asking for here is uninlined_format_args:
$ cargo clippy Checking chatterbox v0.7.0 (~/chatterbox) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s $ cargo clippy -- -W clippy::uninlined_format_args Checking chatterbox v0.7.0 (~/chatterbox) warning: variables can be used directly in the `format!` string --> src/main.rs:77:17 | 77 | write!(f, "I don't know the command {}. Try /help.", word) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#uninlined_format_args = note: requested on the command line with `-W clippy::uninlined-format-args` help: change this to | 77 - write!(f, "I don't know the command {}. Try /help.", word) 77 + write!(f, "I don't know the command {word}. Try /help.") | [... 15 more warnings of the same kind ...] warning: variables can be used directly in the `format!` string --> src/main.rs:340:5 | 340 | println!("messages so far: {}", messages_seen); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.99.0/index.html#uninlined_format_args help: change this to | 340 - println!("messages so far: {}", messages_seen); 340 + println!("messages so far: {messages_seen}"); | warning: `chatterbox` (bin "chatterbox") generated 17 warnings (run `cargo clippy --fix --bin "chatterbox" -p chatterbox -- -W clippy::uninlined_format_args` to apply 17 suggestions) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
Each warning reads like a compiler message: where, what, and a suggested change with the old line in - and the new one in +. This lint is about something you already know: a name can go straight inside the braces, {word}, instead of an empty {} with the name listed after the text. The printed result is identical. The difference is for the reader, who no longer has to count holes and match them to names. It only works for plain names: {} filled by m.to_line() or VERSION.0 has to stay as it is, which is why clippy left those alone.
A bigger group, -W clippy::pedantic, is stricter and more opinionated. On the same code it reports 22 warnings: the same 17, plus five more, such as "this function has too many lines (135/100)" about main. Pedantic lints are advice, not rules. Read them, take the ones that make the code clearer, and leave the rest.
Letting clippy make the change
Seventeen edits by hand is tedious and easy to get wrong. Most lints know their exact fix, so clippy can apply them for you. Add --fix:
$ cargo clippy --fix -- -W clippy::uninlined_format_args
Checking chatterbox v0.7.0 (~/chatterbox)
Fixed src/main.rs (17 fixes)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12sChatterbox v0.7, tidied
This is Chatterbox after that one command. Every green line is a format string that clippy rewrote, seventeen of them, from "bot: bye, {}!", name to "bot: bye, {name}!". Nothing else changed: same behaviour, same seven tests passing, same answers in the run below. From here on, every Chatterbox in the course is clippy-clean with this lint switched on, and rustfmt-formatted.
Sam /remember pizza pepperoni please /recall pizza /recall cat /remember dog woof /recall /quit
Chatterbox v0.7 What should I call you? Hi Sam! I'm Chatterbox. Ask me anything. bot: got it: pizza is pepperoni please bot: pizza is pepperoni please bot: I don't know anything about cat bot: got it: dog is woof bot: I remember: dog, pizza bot: bye, Sam! messages so far: 0
Download this version (zip)Unzip, then cargo run inside the folder.
Looking inside: dbg!
Tests tell you that something is wrong; they do not always tell you why. When a value is not what you expected, the quickest question to ask is "what is it, right here?". Rust has a macro for exactly that: dbg!(x) prints the file, the line, the code you wrote and the value, in the debug form from lesson 5.5. Then it hands the value straight back, so you can wrap it around almost anything without changing what the program does. Try it.
What does this program print on its normal output?
550[src/main.rs:2:13] 2 + 3 = 5
Reveal the answer
The program prints: 50 dbg!(2 + 3) hands back 5, so n is 50. Its report, [src/main.rs:2:13] 2 + 3 = 5, goes to a second channel called standard error, which terminals show alongside the normal output but programs keep separate. So the normal output is just 50.
Here is dbg! earning its keep. Someone writes a word counter that splits text at every space, and it says "hi there", with two spaces, has three words. One dbg! line shows why:
$ cat src/main.rs fn count_words(text: &str) -> usize { let pieces: Vec<&str> = text.split(' ').collect(); dbg!(&pieces); pieces.len() } fn main() { let n = count_words("hi there"); println!("{n} words"); } $ cargo run -q [src/main.rs:3:5] &pieces = [ "hi", "", "there", ] 3 words
There it is: an empty piece between the two spaces. Splitting at every single space makes an empty word wherever two spaces meet. Pass &pieces rather than pieces, so dbg! only borrows the list and the next line can still use it. When the bug is fixed, delete the dbg!: it is for you, not for the people using the program. (Clippy even has an optional lint, clippy::dbg_macro, that finds any you forgot.)
This is the counter from above. It is wrong for "hi there" (two spaces), for text with spaces at the ends, and for empty text, which it says has one word. Add a dbg! to see the pieces if you like, then fix it so that runs of spaces never make empty words. Chatterbox's own /stats already does this the right way.
This is the counter from above. It is wrong for "hi there" (two spaces), for text with spaces at the ends, and for empty text, which it says has one word. Add a dbg! to see the pieces if you like, then fix it so that runs of spaces never make empty words. Chatterbox's own /stats already does this the right way.
Hidden tests (4): one_space_between_words empty_text_has_no_words spaces_at_the_ends_are_not_words two_spaces_are_not_a_word
This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.
Reading the manual
The last tool is the documentation. Everything in the standard library, every type and every method, is described at doc.rust-lang.org/std, usually with a small example. A crate is one package of Rust code: Chatterbox itself is a crate, and so is each library someone publishes for others to use. Every published crate, like the ones you will add in chapter 9, has the same kind of pages on docs.rs, built automatically.
And your own code has them too. The /// comments above Chatterbox's functions have been doc comments all along: cargo doc --open turns them into the same kind of web pages for your project and everything it uses, and opens them in your browser. Try it on Chatterbox: reply, parse and Message each get a page, with your sentence at the top.
Say it out loud, then reveal
What does rustfmt change, and what does it never change?Reveal
What is a clippy lint?Reveal
Why can <code>{}</code> filled by <code>m.to_line()</code> not become an inline argument?Reveal
What does <code>dbg!(x)</code> give back, and where does its report go?Reveal
x unchanged. The report (file, line, code and value) goes to standard error, not the normal output.That is lesson 7.4. Tick it off so the course remembers where you are.
Go deeper
Questions people ask
How do I format Rust code?
Run cargo fmt in the project folder. It rewrites every file into the standard Rust style. cargo fmt --check only reports what it would change, which is handy before sharing code.
What is clippy in Rust?
Clippy is a tool that comes with Rust and checks your code against hundreds of lints, small rules about clear and usual Rust. Run it with cargo clippy and read its suggestions like compiler messages.
How do I make clippy fix the warnings for me?
Run cargo clippy --fix. It applies every suggestion it is sure about and edits your files, so it asks for your work to be saved in git first unless you add --allow-dirty.
What does dbg! do in Rust?
dbg! prints the file, line, the expression you wrote and its value to standard error, then returns the value unchanged. It is for looking inside a program while you find a bug; remove it afterwards.
Where is the documentation for Rust crates?
Every published crate has documentation on docs.rs. The standard library is documented at doc.rust-lang.org/std, and cargo doc --open builds the documentation for your own project.