Rust Coding Style and Naming Conventions

Rust code must adhere to the official style guide enforced by `rustfmt`, which automatically formats code to a consistent standard, while naming conventions follow strict camelCase for functions/variables and PascalCase for types.

When style is part of the contract

You submit your first pull request to a Rust project. The code compiles. The tests pass. Then the CI bot comments back with a wall of red text complaining about getUserData and inconsistent indentation. You didn't break logic; you broke style. In Python or JavaScript, style is often a team preference. In Rust, style is a shared contract. The compiler doesn't care about your naming, but the tooling does, and the community expects you to speak the same dialect.

Rust treats code formatting and naming as part of the language definition, not a personal choice. rustfmt is the official formatter. It's not a suggestion; it's the standard. If you format manually, you're fighting the current. Naming follows strict patterns based on the role of the identifier. snake_case for things you call or hold. PascalCase for types. SCREAMING_SNAKE_CASE for constants. This isn't arbitrary. It lets you scan code and instantly know what something is without reading the definition.

Style is a shared contract. Follow it, and the community reads your code like their own.

The naming dialect

Rust uses visual cues to separate concepts. Types represent nouns in your domain. Functions and variables represent actions or data. The naming conventions make this distinction obvious at a glance.

Types use PascalCase. This includes structs, enums, traits, and type aliases. When you see Vec<T> or Result<T, E>, the capitalization signals that these are types. It helps your brain parse generic parameters and return types faster.

Functions, variables, modules, and struct fields use snake_case. These are the things you interact with. get_user_data, user_config, network_client. The lowercase with underscores keeps the API surface consistent.

Constants use SCREAMING_SNAKE_CASE. These are global, immutable values. MAX_RETRIES, DEFAULT_TIMEOUT. The shouting case signals that the value is fixed and shared.

Think of it like a library. The building names are capitalized (PascalCase). The rooms inside are lowercase (snake_case). The signs on the walls telling you the rules are all caps (SCREAMING_SNAKE_CASE).

Minimal example

Here is how the conventions look in a single file. Every identifier follows the rule for its role.

// Constants use SCREAMING_SNAKE_CASE.
// They are global and immutable.
const MAX_RETRIES: u32 = 3;

// Types use PascalCase.
// This includes structs, enums, and traits.
struct UserConfig {
    // Fields use snake_case.
    // They are data held by the type.
    username: String,
    is_active: bool,
}

// Functions use snake_case.
// They represent actions.
fn process_user(config: &UserConfig) -> Result<(), String> {
    // Local variables also use snake_case.
    let user_name = config.username.clone();
    
    // Use the constant for configuration.
    let retries = MAX_RETRIES;
    
    if config.is_active {
        println!("Processing {} with {} retries", user_name, retries);
    }
    
    Ok(())
}

Run cargo fmt before every commit. Manual formatting is a merge conflict waiting to happen.

How the tooling enforces idioms

Rust ships with two tools that keep your code idiomatic. rustfmt handles formatting. clippy handles linting. You install them via rustup.

# Add the formatter and linter to your toolchain.
rustup component add rustfmt clippy

When you run cargo fmt, rustfmt rewrites your source files to match the official style. It handles indentation, line breaks, and spacing. You don't configure tabs vs spaces; the tool decides. The community convention is to let rustfmt run on every file. This eliminates style debates. You don't argue about indentation; you argue about logic.

When you run cargo clippy, the linter checks for idiomatic patterns. It will warn you if you name a function getUserData. The warning isn't a compile error, but it's a signal that your code looks foreign. Clippy suggests the correct name.

# Format the project.
cargo fmt

# Run lints to check for style and naming issues.
cargo clippy

Don't argue style; argue logic. The tools handle the rest.

Realistic module structure

In a real project, naming conventions apply across modules and implementations. Consistency makes the codebase navigable.

// Module names use snake_case.
// Modules act as namespaces.
mod network_client {
    // Types inside the module use PascalCase.
    pub struct HttpClient {
        // Fields use snake_case.
        base_url: String,
        timeout_seconds: u64,
    }

    // Constants use SCREAMING_SNAKE_CASE.
    const DEFAULT_TIMEOUT: u64 = 30;

    impl HttpClient {
        // Constructor methods use snake_case.
        // `new` is the standard name for constructors.
        pub fn new(base_url: String) -> Self {
            Self {
                base_url,
                // Use the constant for the default value.
                timeout_seconds: DEFAULT_TIMEOUT,
            }
        }

        // Public methods use snake_case.
        pub fn fetch_data(&self, path: &str) -> Result<String, Box<dyn std::error::Error>> {
            // Implementation details...
            Ok(format!("{}{}", self.base_url, path))
        }
    }
}

// Usage in main.
fn main() {
    // Access the module and type.
    let client = network_client::HttpClient::new("https://api.example.com".to_string());
    
    // Call the method.
    match client.fetch_data("/users") {
        Ok(data) => println!("Got data: {}", data),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Consistency beats cleverness. Stick to the patterns, and your code reads like standard Rust.

Acronyms and the clippy trap

Rust has a specific rule for acronyms that trips up many developers. Acronyms get downcased except for the first letter.

IOError becomes IoError. XMLParser becomes XmlParser. HTTPSClient becomes HttpsClient. The first letter is capitalized, the rest are lowercase. This keeps the visual rhythm consistent. IOError looks like two separate words or a typo. IoError looks like a proper type name.

Clippy enforces this with the clippy::upper_case_acronyms lint. If you write struct IOError, clippy will warn you.

// BAD: Triggers clippy::upper_case_acronyms.
struct IOError {
    code: u32,
}

// GOOD: Follows the acronym convention.
struct IoError {
    code: u32,
}

Downcase the acronym. The compiler doesn't care, but the linter does.

Macros and the bang

Macros are a unique category. They use snake_case for the name, just like functions. The difference is the invocation. You call macros with a bang !.

// Macro definition uses snake_case.
macro_rules! log_debug {
    ($msg:expr) => {
        println!("[DEBUG] {}", $msg);
    };
}

// Invocation uses the name followed by a bang.
fn main() {
    log_debug!("Starting process");
}

Macros are functions with a bang. Name them like functions.

Configuration: When to break the rules

You can configure rustfmt using a rustfmt.toml file in your project root. The community convention is to avoid configuration unless you have a compelling reason. Deviating from defaults makes your code harder for others to read.

Common configuration is max_width. The default is 100 characters. Some teams prefer 80 or 120. If you change this, document it.

# rustfmt.toml
# Only set max_width if your team has a strong preference.
max_width = 100
edition = "2021"

Another convention aside involves cloning reference-counted types. When cloning an Rc<T> or Arc<T>, use the explicit form Rc::clone(&data) instead of data.clone(). Both compile and work. The explicit form signals to readers that you are cloning the reference, not the data. data.clone() looks like a deep clone, which can be misleading.

use std::rc::Rc;

fn main() {
    let data = Rc::new(vec![1, 2, 3]);
    
    // Convention: Use explicit Rc::clone for clarity.
    // This signals a shallow clone of the reference.
    let data_clone = Rc::clone(&data);
}

Only configure formatting if you have a compelling reason. Default is best.

Pitfalls and lints

Using camelCase for a function like getUserData triggers a clippy::naming_conventions warning. The linter suggests get_user_data. Naming a struct userConfig instead of UserConfig also triggers this warning. The compiler won't stop you, but the linter will nag you until you fix it.

Ignoring clippy warnings leads to code that feels foreign. Other Rust developers will scan your code and immediately spot the inconsistencies. It creates cognitive friction.

Trust the linter. If clippy complains about naming, it's not being pedantic; it's protecting readability.

Decision matrix

Use snake_case for functions, variables, modules, and struct fields. This covers the vast majority of identifiers in your code. Use PascalCase for types, including structs, enums, traits, and type aliases. This makes types stand out visually in generic parameters and return types. Use SCREAMING_SNAKE_CASE for constants, both module-level const and static items. This signals that the value is global and immutable. Use camelCase only when interacting with external APIs that require it, such as JSON keys or FFI bindings, and isolate those names behind a safe Rust interface.

Stick to the conventions. Deviating makes your code look like a translation error.

Where to go next