4.6 Naming Conventions: snake_case, SCREAMING_SNAKE_CASE
Right, let’s talk about naming things. It’s one of the two hard problems in computer science, the others being cache invalidation and off-by-one errors. But unlike cache invalidation, naming is something you have complete control over, and doing it well is the first step toward writing code that doesn’t make you want to claw your eyes out six months later.
Rust has a few hard rules and a lot of strong conventions. The compiler will enforce the rules; the linter (clippy) will gently (or not-so-gently) suggest you follow the conventions. And you should listen. Conventions exist so that any Rustacean, anywhere, can open your code and immediately understand its structure without a Rosetta stone. It’s a shared language of clarity.
The Rules: What the Compiler Demands
First, the non-negotiable stuff. A variable name must start with a letter or an underscore, and after that, it can contain letters, digits, and underscores. You can’t use keywords like fn, let, or mut as names. That’s about it. Technically, you could name a variable _a1_b_c if you had a deeply compelling reason, but please, for the sake of everyone including future-you, don’t.
The compiler doesn’t care about case, but the conventions do, and they matter just as much.
snake_case for… Pretty Much Everything
In Rust, we use snake_case for almost all variable and function names. That means all lowercase, with words separated by underscores. This isn’t the camelCase-dominated world of JavaScript or Java. Why? Readability and consistency.
// Good. Clean. Legible. The Rust way.
let user_name = "Ferb";
let maximum_connection_count = 10;
let is_connected_to_database = true;
// The compiler will accept this... but `clippy` will yell at you,
// and other Rust developers will give you a look of profound disappointment.
// Don't do this.
let userName = "Ferb";
let MaximumConnectionCount = 10;
let IsConnectedToDatabase = true;
The logic is simple: it’s easier to quickly parse a long name when every word break is uniformly signaled by an underscore. CamelCase can create ambiguity—does parseHTMLRequest break down as parse_HTML_Request or parse_H_T_M_L_Request? snake_case eliminates that guesswork entirely. It’s boringly, beautifully consistent.
SCREAMING_SNAKE_CASE for Constants
Now, for when you need to shout. SCREAMING_SNAKE_CASE (all caps, words separated by underscores) is reserved for constants and static variables. This is a universal signal in the code: “This value is fixed, global, and should not change.”
const MAX_ALLOWED_CONNECTIONS: u32 = 100;
const DATABASE_URL: &str = "postgres://localhost:5432";
fn main() {
// This is a runtime variable. It might change, so it's snake_case.
let current_connections = 5;
println!("You can have {} more connections.", MAX_ALLOWED_CONNECTIONS - current_connections);
}
Notice the key difference here: we use const for these, not let. A const is a true constant. It’s inlined at compile time and has no memory address. There’s also static, for global variables that do have a fixed memory address, and it follows the same naming convention. The screaming case immediately tells you, “Hey, this isn’t some mundane variable floating around in a function. This is a global setting. Tread carefully.”
The Underscore: _ and _variable
The humble underscore is a special character in Rust. If you prepend a variable name with it, you’re telling the compiler, “I know I’m not using this, please shut up about it.” This is how you silence the infamous “unused variable” warning without just removing the variable, which is useful for things you’re planning to use later or for patterns where you need to bind a value but ignore most of it.
fn calculate_thing(value: i32) -> i32 {
let _unused_but_planned_for_future = 42; // Warning silenced.
let normal_unused_variable = 13; // This will trigger a warning.
// Also useful in patterns: we only care about the error variant, not its contents.
let result: Result<i32, String> = Err("Failed!".to_string());
if let Err(_) = result {
println!("It failed, but I don't care about the error text right now.");
}
value * 2
}
There’s a crucial distinction: _ on its own is a completely different beast. It doesn’t bind the value at all; it throws it away immediately. This isn’t a variable you can use later.
let value = Some(5);
// Using `_` alone: the `5` is truly discarded. You can't use it.
if let Some(_) = value {
// println!("The value is {}", _); // ERROR: can't use `_` as a value!
}
// Using `_name`: it's a real variable, just with a silenced warning.
if let Some(_inner_value) = value {
// println!("The value is {}", _inner_value); // This is perfectly fine.
}
Best Practices and Pitfalls
Be Descriptive, But Not Absurd:
numis bad.connection_countis good.number_of_active_database_connection_instancesis probably too much. Find the sweet spot.Booleans are Questions: Name them like yes/no questions.
is_ready,has_value,can_execute. This makesifstatements read like English:if is_ready { ... }.Avoid Accidental Shadowing: While shadowing is a feature, doing it accidentally with similarly named variables is a classic bug. The compiler allows this, so your vigilance is required.
let value = "hello"; // ... 100 lines of code later ... let value = value.len(); // This is allowed, but did you *mean* to shadow the string with its length?Just Follow
clippy: Seriously. Runcargo clippyon your code. It’s not an insult; it’s a free expert code review. It will catch deviations from convention and suggest more idiomatic names and patterns. Embrace it. Your code will be better for it.