1.8Keywords and naming identifiers
Every name in your program (variables so far; functions and types soon) is an identifier, and this lesson covers what identifiers may look like, plus the short list of words you can't have because Rust got there first.
Keywords
A keyword is a word with reserved meaning in the language. You've used six already: fn, let, mut, use, and the value-words true and false (coming in chapter 4). The 2024 edition reserves these:
as async await break const continue crate
dyn else enum extern false fn for
if impl in let loop match mod
move mut pub ref return self Self
static struct super trait true type unsafe
use where while
(Plus the lone underscore _, which acts as a special "deliberately unnamed" marker you've already brushed against in _unused.) Don't memorize the table; you'll absorb it by collision. A keyword can't be an identifier, and the compiler is blunt about it:
fn main() {
let fn = 5;
println!("{fn}");
}error: expected identifier, found keyword `fn`
--> src/main.rs:2:9
|
2 | let fn = 5;
| ^^ expected identifier, found keywordFor advanced readers
A second tier of words (abstract, become, box, do, final, gen, macro, override, priv, try, typeof, unsized, virtual, yield) is reserved for the future: unusable today, not yet meaning anything. This is editions machinery from lesson 0.12 working as designed, parking words now so claiming them later breaks nothing. And if you ever must use a keyword as a name (say, a field called type in data from elsewhere), raw identifier syntax r#type exists. File under "good to know it exists."
The rules
The hard rules for identifiers are short: letters, digits, and underscores only; it can't start with a digit; and it can't be a keyword. Identifiers are case-sensitive, so score, Score, and SCORE are three different names (defining all three is legal, and a misdemeanor).
The conventions
The rules leave room for chaos, so the community settled conventions, and here Rust does something unusual: the compiler itself nudges you. Variables and functions are written in snake_case: lowercase words joined by underscores. Type names (chapter 10) get UpperCamelCase, and constants (lesson 5.1) get SCREAMING_SNAKE_CASE. Stray from the pattern and you get a warning:
fn main() {
let playerName = "Ada";
println!("{playerName}");
}warning: variable `playerName` should have a snake case name
--> src/main.rs:2:9
|
2 | let playerName = "Ada";
| ^^^^^^^^^^ help: convert the identifier to snake case: `player_name`
|
= note: `#[warn(non_snake_case)]` (part of `#[warn(nonstandard_style)]`) on by default
The program runs, but the message is clear, and so is the ecosystem's expectation: all Rust code everywhere uses these conventions, which is why code you find online always looks like the code in this course. (Veterans of other languages' brace-and-case wars will recognize what a gift that is.)
Beyond case, good names are a judgment call, and the judgment improves with a few principles. Make the length of a name proportional to its importance: a loop counter alive for two lines can be i; a value used across a whole function deserves a word; something central to the program deserves a good one. Name the thing, not the type (elapsed_seconds beats the_number). And don't abbreviate to save four keystrokes that cost every future reader a guess.
A field guide, in the spirit of an old learncpp.com table:
| Declaration | Verdict |
|---|---|
let elapsed_seconds = 4; | conventional |
let s = 4; | fine in a two-line scrap, cryptic anywhere else |
let elapsedSeconds = 4; | works, but the compiler will tut (snake case, please) |
let ELAPSED = 4; | shouting is for constants (lesson 5.1) |
let _elapsed = 4; | the underscore says "unused on purpose," so only when it is |
let seconds_elapsed_since_the_program_started_running = 4; | technically legal, like a 40-character password |
let 2fast = 4; | illegal: starts with a digit |
let player name = 4; | illegal: spaces can't appear in identifiers |
Best practice
Name things in snake_case, descriptively, as if the reader knows nothing. The three seconds you spend choosing remaining_attempts over ra are repaid every single time anyone (including you) reads the line.
Quiz time
Question #1
Judge each: conventional, legal-but-unconventional, or illegal?
a) let user_count = 10;
b) let UserCount = 10;
c) let 3d_points = 10;
d) let match = 10;
e) let x = 10; (as a program's central value, used in 30 places)
Show solution
a) Conventional.
b) Legal but unconventional for a variable; that casing belongs to types, and the compiler warns.
c) Illegal: identifiers can't start with a digit.
d) Illegal: match is a keyword.
e) Legal and conventionally shaped, but a bad name for something used 30 times; one-letter names are for tiny scopes.
Question #2
Predict what the compiler says about this program (it does compile):
fn main() {
let finalScore = 95;
println!("{finalScore}");
}Show solution
A non_snake_case warning: variable finalScore should have a snake case name, with the help line suggesting final_score. The program still compiles and prints 95.
Names sorted. Next, a quick lesson on the space between the names, and the tool that ends every argument about it.