Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust FAQ: Style Guidelines

PART13 -- Style Guidelines

Q81: What are some good Rust coding standards?

Rust has a strong emphasis on consistent, readable, and safe code. The Rust community follows well-established coding standards, primarily based on the Rust Style Guide and tools like rustfmt and clippy. Here are some key Rust coding standards:

  • Formatting:

    • Use rustfmt to automatically format code according to the official Rust style guide:
      • 4-space indentation (no tabs).
      • One statement per line.
      • Braces on the same line for blocks (e.g., fn main() {).
      • Run cargo fmt to apply formatting.
    • Example:
      #![allow(unused)]
      fn main() {
      fn add(a: i32, b: i32) -> i32 {
          a + b
      }
      }
  • Naming Conventions:

    • Snake case for functions, variables, and modules: my_function, my_variable, my_module.
    • Pascal case for types and traits: MyStruct, MyTrait.
    • Screaming snake case for constants: MY_CONSTANT.
    • Use meaningful, concise names that describe intent (e.g., calculate_area over calc).
  • Code Organization:

    • Group related code in modules using mod and pub for visibility.
    • Place each module in its own file (e.g., src/shapes.rs for mod shapes).
    • Use pub sparingly to maintain encapsulation; prefer pub(crate) for crate-internal visibility.
  • Error Handling:

    • Use Result and Option for explicit error handling instead of panics.
    • Avoid unwrap() or expect() in production code; use ? or match instead.
    • Example:
      #![allow(unused)]
      fn main() {
      fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
          s.parse()
      }
      }
  • Safety and Idioms:

    • Prefer safe Rust; use unsafe only when necessary (e.g., FFI or low-level code).
    • Use traits for polymorphism and composition over inheritance.
    • Avoid excessive cloning; use references (&T, &mut T) when possible.
    • Leverage iterators and functional patterns (e.g., map, filter) for concise, readable code.
  • Documentation:

    • Write doc comments with /// for public APIs and //! for module-level docs.
    • Include examples in doc comments, testable with cargo test.
    • Example:
      #![allow(unused)]
      fn main() {
      /// Calculates the square of a number.
      /// # Examples
      /// ```
      /// assert_eq!(square(4), 16);
      /// ```
      pub fn square(x: i32) -> i32 {
          x * x
      }
      }
  • Tooling:

    • Run clippy (cargo clippy) to catch common mistakes and enforce idiomatic Rust.
    • Use cargo check or cargo build frequently to catch errors early.
    • Enable warnings with #![deny(warnings)] in lib.rs or main.rs for strict adherence.

Resources:

  • Official Rust Style Guide (used by rustfmt).
  • Rust API Guidelines (https://rust-lang.github.io/api-guidelines/).
  • Clippy documentation for linting rules.

These standards ensure code is readable, maintainable, and idiomatic, aligning with Rust’s safety and performance goals.

Q82: Are coding standards necessary? Sufficient?

Are coding standards necessary? Yes, coding standards are necessary in Rust for several reasons:

  • Readability: Consistent formatting and naming make code easier to understand, especially in teams or open-source projects.
  • Maintainability: Standards reduce friction when refactoring or extending code, as everyone follows the same conventions.
  • Safety: Idiomatic Rust (enforced by standards) encourages safe patterns, like using Result over unwrap, reducing bugs.
  • Collaboration: Standards ensure contributors (e.g., in open-source crates) produce consistent code, improving project cohesion.
  • Tooling: Tools like rustfmt and clippy enforce standards automatically, catching errors and improving quality.

Are coding standards sufficient? No, coding standards alone are not sufficient to ensure high-quality Rust code:

  • Correctness: Standards guide style but don’t guarantee logical correctness or proper use of Rust’s ownership model.
  • Design: Good architecture (e.g., proper use of traits, modules, or error handling) requires understanding Rust’s idioms beyond formatting.
  • Testing: Standards don’t replace the need for unit tests, integration tests, or property-based testing to verify behavior.
  • Performance: Standards may suggest efficient patterns, but optimizing for specific use cases requires profiling and analysis.
  • Context: Standards are general; project-specific requirements (e.g., real-time constraints) may need additional guidelines.

Example:

#![allow(unused)]
fn main() {
// Standard-compliant but incorrect
fn divide(a: i32, b: i32) -> i32 {
    a / b // Fails if b is 0
}

// Better, with error handling
fn divide_safe(a: i32, b: i32) -> Result<i32, &'static str> {
    if b == 0 {
        Err("Division by zero")
    } else {
        Ok(a / b)
    }
}
}

Standards ensure divide follows naming and formatting rules, but only proper design ensures it handles division by zero.

Conclusion:

  • Necessary: Standards are critical for consistency and collaboration.
  • Not Sufficient: Combine standards with testing, good design, and project-specific guidelines for robust code.

Q83: Should our organization base Rust standards on C++ experience?

Basing Rust coding standards on C++ experience can be partially helpful but should be approached cautiously, as Rust and C++ have different philosophies, type systems, and idioms. Here’s how to approach it:

When C++ experience helps:

  • General Principles: Some C++ practices translate well, like:
    • Consistent naming (e.g., snake_case for functions, PascalCase for types, though Rust’s conventions differ slightly).
    • Modular code organization (e.g., separating code into files, similar to Rust’s modules).
    • Documentation standards (e.g., writing clear comments, though Rust uses /// for doc comments).
  • Performance Awareness: C++’s focus on low-level control can inform Rust’s performance-critical code, like minimizing allocations or using unsafe wisely.
  • Team Familiarity: If your team is experienced in C++, adapting familiar conventions (e.g., file organization) can ease the transition to Rust.

Why not rely entirely on C++ standards:

  • Different Paradigms:
    • Rust’s ownership model (no garbage collector, strict borrowing) requires idioms that C++ doesn’t have, like avoiding raw pointers in favor of Box or Rc.
    • Rust avoids inheritance, favoring traits and composition, unlike C++’s class hierarchies.
  • Safety Focus: Rust’s standards prioritize memory safety (e.g., avoiding unsafe unless necessary), while C++ often requires manual memory management.
  • Tooling: Rust’s rustfmt and clippy enforce idiomatic style automatically, unlike C++’s varied linters (e.g., clang-format).
  • Error Handling: Rust uses Result and Option instead of C++’s exceptions or error codes, requiring different patterns.

Recommendations:

  • Adopt Rust-Specific Standards:
    • Follow the Rust Style Guide and use rustfmt for formatting.
    • Use clippy to enforce idiomatic Rust (e.g., avoid unwrap, prefer iterators).
  • Adapt C++ Experience Selectively:
    • Keep universal good practices (e.g., clear naming, modular design).
    • Avoid C++ idioms like manual memory management (new/delete) or deep inheritance.
  • Train the Team: Educate C++ developers on Rust’s ownership, borrowing, and trait systems to avoid carrying over incompatible patterns.
  • Example:
    #![allow(unused)]
    fn main() {
    // C++-style (avoid in Rust)
    fn get_value(ptr: *const i32) -> i32 {
        unsafe { *ptr }
    }
    
    // Rust-idiomatic
    fn get_value(value: &i32) -> i32 {
        *value
    }
    }

Best Practice:

  • Start with Rust’s official style guide and tools.
  • Incorporate C++ experience for general principles (e.g., naming, modularity) but prioritize Rust idioms.
  • Iterate on standards based on team feedback and project needs.

Q84: Should I declare variables in the middle of a function or at the top?

In Rust, you should declare variables as close to their use as possible (i.e., in the middle of a function) rather than at the top, following modern programming practices and Rust’s idioms. Here’s why and how:

Why declare variables near use:

  • Readability: Declaring variables where they’re used makes code easier to follow, as the variable’s purpose is clear from context.
  • Scope Minimization: Rust’s block-scoped variables (let) mean variables declared later have a smaller scope, reducing the chance of accidental misuse.
  • Ownership and Borrowing: Declaring variables close to use aligns with Rust’s ownership model, making it easier to manage lifetimes and avoid borrow checker errors.
  • Performance: Declaring variables only when needed avoids unnecessary allocations until required.
  • Idiomatic Rust: The Rust community favors this style, as seen in the Rust Style Guide and rustfmt.

Example:

#![allow(unused)]
fn main() {
fn process_data(input: &str) -> i32 {
    // Declare variables where needed
    let parsed: i32 = input.parse().unwrap(); // Near use
    let result = parsed * 2;
    result
}
}

Why avoid declaring at the top:

  • Clutter: Declaring all variables at the top (common in older C/C++ code) makes functions harder to read, especially with many variables.
  • Ownership Issues: Early declarations can lead to longer lifetimes, causing borrow checker conflicts:
    #![allow(unused)]
    fn main() {
    fn bad_example(input: &str) -> i32 {
        let parsed: i32; // Declared at top
        let result: i32;
        parsed = input.parse().unwrap(); // Assigned later
        result = parsed * 2;
        result
    }
    }
    This is less clear and risks uninitialized variable errors if you forget to assign.

Exceptions:

  • Initialization Logic: If a variable requires complex initialization spanning multiple lines, declare it early to group logic:
    #![allow(unused)]
    fn main() {
    fn complex_init() {
        let config: Config;
        if some_condition() {
            config = load_config();
        } else {
            config = default_config();
        }
        // Use config
    }
    }
  • Legacy Code: If your team comes from a C/C++ background, declaring at the top might feel familiar, but it’s not idiomatic in Rust.

Best Practice:

  • Declare variables close to their first use to keep code clear and lifetimes short.
  • Use rustfmt to ensure consistent formatting.
  • Avoid uninitialized variables unless necessary, and let the compiler catch errors.

Q85: What file-name convention is best? foo.rs? foo.rust?

The best file-name convention in Rust is foo.rs. Here’s why:

  • Standard Practice: The Rust community universally uses the .rs extension for Rust source files, as specified in the Rust Style Guide and used in the Rust repository and crates.io.
  • Tooling Support: Cargo, rustc, rustfmt, and other tools expect .rs files by default. Using .rust may cause issues with build tools or IDEs.
  • Clarity: .rs is short, clear, and associated with Rust, avoiding confusion with other languages.
  • Precedent: Official Rust projects (e.g., std, tokio, serde) all use .rs.

Why avoid foo.rust?

  • Non-standard and not recognized by Rust tools.
  • Less common in the ecosystem, reducing consistency.
  • Potentially confusing with other languages or tools.

Example File Structure:

my_crate/
├── src/
│   ├── main.rs      # Entry point
│   ├── lib.rs       # Library entry point
│   ├── shapes.rs    # Module file
│   └── utils.rs     # Another module

Best Practice:

  • Use foo.rs for all Rust source files.
  • Match the file name to the module name (e.g., mod shapes in shapes.rs).
  • Use main.rs for binary crates and lib.rs for library crates.

Q86: What module naming convention is best? foo_mod.rs? foo.rs?

The best module naming convention in Rust is foo.rs, not foo_mod.rs. Here’s why:

  • Standard Convention: The Rust Style Guide and community practice favor foo.rs for module files, where the file name matches the module name declared with mod foo;.
  • Simplicity: foo.rs is concise and avoids redundant suffixes like _mod. The mod keyword already indicates it’s a module.
  • Tooling: Cargo and rustc expect module files to match their mod declarations (e.g., mod foo looks for foo.rs or foo/mod.rs).
  • Clarity: Using foo.rs aligns with the module’s name in code, making it easier to navigate projects.

Example:

my_crate/
├── src/
│   ├── main.rs
│   ├── shapes.rs  # Contains `mod shapes`
│   └── utils/
│       ├── mod.rs  # Declares `mod utils`
│       └── helpers.rs  # Declares `mod helpers` inside `utils`

In main.rs:

mod shapes;
mod utils;

fn main() {
    shapes::some_function();
}

Why avoid foo_mod.rs?

  • Redundant: The _mod suffix adds no useful information.
  • Non-idiomatic: It deviates from Rust’s standard naming, reducing consistency.
  • Less common: Most Rust projects (e.g., std, tokio) use foo.rs.

Alternative: Directory-based Modules: For larger modules, use a directory with a mod.rs file:

src/
├── shapes/
│   ├── mod.rs  # Declares `mod shapes`
│   ├── circle.rs  # Submodule `mod circle`

Best Practice:

  • Use foo.rs for single-file modules (e.g., mod foo in foo.rs).
  • Use mod.rs for directory-based modules with submodules.
  • Avoid foo_mod.rs to keep naming simple and idiomatic.

Q87: Are there any clippy-like guidelines for Rust?

Yes, Rust has several Clippy-like guidelines and tools to enforce idiomatic, safe, and maintainable code. The primary tool is Clippy, a linter included with Rust, but other resources and practices complement it. Here’s an overview:

  • Clippy:

    • Clippy is a collection of lints that catch common mistakes, enforce idiomatic Rust, and suggest improvements.
    • Run with cargo clippy.
    • Key lints include:
      • pedantic: Enforces strict style and idiom checks (e.g., avoiding unwrap).
      • style: Suggests clearer code (e.g., use is_empty over len() == 0).
      • correctness: Catches potential bugs (e.g., invalid dereferencing).
      • complexity: Flags overly complex code (e.g., nested loops that could be simplified).
    • Example:
      #![allow(unused)]
      fn main() {
      let v = vec![1, 2, 3];
      if v.len() == 0 { // Clippy warns: use `is_empty`
          println!("Empty");
      }
      }
      Clippy suggests: if v.is_empty() { ... }
  • Rust API Guidelines:

    • The Rust API Guidelines (https://rust-lang.github.io/api-guidelines/) provide high-level recommendations for library authors:
      • Naming: Use clear, concise names (e.g., to_string over convert_to_string).
      • Interoperability: Make types work with standard traits like Debug, Clone.
      • Error Handling: Use Result for fallible operations.
      • Flexibility: Prefer generics over concrete types where possible.
    • These are not enforced by tools but are considered best practices.
  • Rust Style Guide:

    • The official Rust Style Guide defines formatting rules (enforced by rustfmt):
      • 4-space indentation.
      • Consistent brace placement.
      • Snake case for functions, Pascal case for types.
    • Run cargo fmt to apply.
  • Other Tools:

    • rust-analyzer: An IDE plugin that suggests improvements and catches issues in real-time.
    • Miri: An interpreter for detecting undefined behavior in unsafe code.
    • cargo-audit: Checks dependencies for known vulnerabilities.
    • cargo-deny: Enforces policies on dependencies (e.g., license compliance).
  • Community Practices:

    • Write testable doc comments with /// (run with cargo test).
    • Use #[deny(warnings)] to enforce clean code with no warnings.
    • Prefer iterators and functional patterns for concise, safe code.
    • Avoid unsafe unless necessary, and document its use thoroughly.

Example with Clippy:

#![allow(unused)]
fn main() {
fn bad_code() {
    let x = String::from("hello").to_string(); // Clippy: redundant allocation
}
}

Clippy suggests: let x = String::from("hello");.

Best Practice:

  • Run cargo clippy --all -- -D warnings to enforce strict linting.
  • Combine Clippy with rustfmt and Rust API Guidelines for consistent, idiomatic code.
  • Regularly check community resources (e.g., Rust Blog, This Week in Rust) for evolving best practices.