Syntax, Comments & Style

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Rust 1.99.0 and the 2024 edition, checked on 2026-10-06. It describes how Rust source text is put together — the tokens the compiler sees, the comment forms, the difference between items and statements, attributes — and the conventions the community follows for naming and layout. The operators are listed in Types & Values and Standard Traits & Operator Overloading.

Source files and tokens

Rust source files are UTF-8 text with the .rs extension. The lexer turns them into tokens: keywords, identifiers, literals, lifetimes ('a), punctuation (::, ->, =>, #, ..= …​) and delimiters ((), [], {}). Whitespace — including newlines — only separates tokens; Rust is not indentation-sensitive. A file may start with a shebang line (#!/usr/bin/env ...), which is ignored.

Keywords

The Reference sorts keywords into three groups.

Strict keywords can only be used as keywords:

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.

Reserved keywords are not used yet but cannot be identifiers, so the language can adopt them later without breaking code:

abstract, become, box, do, final, gen (2024 edition), macro, override, priv, try (2018 edition), typeof, unsized, virtual, yield.

Weak keywords have a special meaning only in certain positions and are otherwise normal identifiers:

  • union — only when declaring a union (union U { ... });

  • 'static — the static lifetime, which cannot be used as a generic lifetime parameter name;

  • safe — only inside unsafe extern blocks (2024 edition);

  • raw — only in &raw const/&raw mut (1.82).

Identifiers and raw identifiers

Identifiers start with a letter or _ followed by letters, digits and _. Non-ASCII identifiers (Unicode XID_Start/XID_Continue, NFC-normalised) are allowed, but rustc warns about confusable or mixed-script names, and the API Guidelines recommend ASCII for public APIs. _ alone is not an identifier but a pattern that discards a value.

A raw identifier r#name lets you use a keyword as a name. It exists mainly for interoperability between editions: when 2018 made async and try keywords, older crates exporting a function called try stayed callable as r#try, and the 2024 edition’s new gen keyword works the same way.

// A crate written for an older edition might export a function named `gen`;
// in a 2024 crate it can still be defined and called with the raw form.
fn r#gen(seed: u64) -> u64 {
    seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1)
}

struct Query {
    r#type: String, // `type` is a keyword; the field is still called `type`
}

fn main() {
    let q = Query { r#type: "title".to_string() };
    println!("{} {}", q.r#type, r#gen(42));
}

Literals

Kind Examples Notes

Integer

98_222, 0xff, 0o77, 0b1111_0000, 57u8, 1_000i64

_ separators are ignored; a suffix fixes the type; without one the type is inferred, defaulting to i32

Float

2.0, 1e-3, 6.022_140_76e23_f64, 0.5f32

default f64; 1. is valid but 1.foo() calls a method on an integer

Character

'a', '\n', '\u{1F4DA}', '\''

a Unicode scalar value, 4 bytes

String

"Dune", "line\n"

UTF-8; a \ at the end of a line skips the newline and the next line’s leading whitespace

Raw string

r"C:\shelf", r#"say "hi""#

no escapes; add # until the content cannot end the literal

Byte, byte string

b'A', b"GET", br"\d"+

u8 and &[u8; N]; ASCII only

C string

c"title", cr"raw"

&'static CStr with a terminating NUL (1.77); see Strings & Text

Boolean

true, false

fn main() {
    let isbn_prefix = 978_u16;
    let flags = 0b0000_0101_u8;
    let mask = 0xFF_u8;
    let price = 24.99_f64;
    let path = r"C:\shelf\books.json";
    let json = r#"{"title": "Dune"}"#;
    let method = b"GET";
    let c_title = c"Dune";
    println!("{isbn_prefix} {flags} {mask} {price} {path} {json} {method:?} {c_title:?}");
}

Comments and doc comments

Rust has two ordinary comment forms, both ignored by the compiler:

  • line comments, // to the end of the line (the idiomatic form);

  • block comments, /* ... */, which nest, so commenting out a block that contains comments is safe.

Doc comments are syntax for the doc attribute, and rustdoc renders them as Markdown:

Form Equivalent Documents

/// and /** ... */

#[doc = "..."]

the item that follows (outer doc comment)

//! and /*! ... */

#![doc = "..."]

the enclosing item — usually the crate in lib.rs or a module (inner doc comment)

//! Utilities for formatting catalogue entries (a crate called `shelf_fmt`).
//!
//! This inner doc comment documents the enclosing module.

/// Formats a title and year as `Title (Year)`.
///
/// # Examples
///
/// ```
/// assert_eq!(shelf_fmt::label("Dune", 1965), "Dune (1965)");
/// ```
pub fn label(title: &str, year: u16) -> String {
    // An ordinary comment: not part of the documentation.
    /* Block comments /* nest */ too. */
    format!("{title} ({year})")
}

fn main() {
    assert_eq!(label("Dune", 1965), "Dune (1965)");
}

Code blocks in doc comments are compiled and run as tests by cargo test; see Documentation with rustdoc for sections, intra-doc links and doctest attributes.

Items, statements and expressions

A Rust source file is a list of items: functions (fn), types (struct, enum, union, type), trait and impl blocks, const and static declarations, modules (mod), use declarations, extern blocks, macro definitions and extern crate. Items are declared at compile time; their order in the file does not matter, and they can also appear inside a function body, where they are only visible to that block.

Inside function bodies, code is a sequence of statements:

  • let statements introduce variables;

  • item declarations;

  • expression statements — an expression followed by ;, evaluated for its side effects;

  • empty statements (;).

Nearly everything else is an expression that produces a value, including blocks, if, match and loop. A block evaluates to its final expression if it has no trailing semicolon. That rule is covered in Functions & Expressions.

fn main() {
    // An item declared inside a function: visible only in this block.
    fn shout(s: &str) -> String {
        s.to_uppercase()
    }

    let title = "dune";                       // let statement
    shout(title);                             // expression statement (result discarded)
    let loud = { let t = shout(title); t + "!" }; // block expression: value of its tail
    println!("{loud}");
}

Attributes

Attributes are metadata attached to items, statements and expressions, written #[name], #[name = "value"] or #[name(args)]. An outer attribute (#[...]) applies to the thing that follows; an inner attribute (#![...]) applies to the enclosing item and must come first in it — typically at the top of lib.rs or main.rs to configure the whole crate. The main families:

Family Examples

Conditional compilation

#[cfg(test)], #[cfg(target_os = "linux")], #[cfg_attr(feature = "serde", derive(Serialize))]

Testing

#[test], #[ignore], #[should_panic]

Derive

#[derive(Debug, Clone, PartialEq)]

Lint levels

#[allow(dead_code)], #[warn(...)], #[deny(...)], #[forbid(...)], #[expect(unused, reason = "...")] (1.81)

Code generation

#[inline], #[cold], #[repr(C)], #[target_feature(enable = "avx2")]

Diagnostics

#[must_use], #[deprecated(since = "0.2", note = "...")], #[diagnostic::on_unimplemented]

Unsafe attributes (2024)

#[unsafe(no_mangle)], #[unsafe(export_name = "...")], #[unsafe(link_section = "...")]

Tool attributes

#[rustfmt::skip], #[clippy::msrv = "1.85"]

Crate-level

#![no_std], #![forbid(unsafe_code)], #![doc(html_root_url = "...")]

#![warn(missing_docs)]
//! A crate whose public items must all be documented.

/// The default loan period in days.
pub const LOAN_DAYS: u16 = 21;

/// Returns the due day for a loan that starts on `day`.
#[must_use]
pub fn due_day(day: u16) -> u16 {
    day + LOAN_DAYS
}

#[cfg(test)]
mod tests {
    #[test]
    fn three_weeks() {
        assert_eq!(super::due_day(1), 22);
    }
}

fn main() {
    let _ = due_day(1);
}

Naming conventions

The compiler enforces naming conventions with warn-by-default lints (non_snake_case, non_camel_case_types, non_upper_case_globals). The API Guidelines spell them out:

Item Convention Example

Crates

snake_case (Cargo package names often use -, which becomes _ in code)

shelf_core

Modules, functions, methods, local variables, fields, macros

snake_case

due_day, println!

Types, traits, enum variants

UpperCamelCase

InMemoryCatalog, LoanState::Overdue

Type parameters

short UpperCamelCase, usually one letter

T, K, V, E

Lifetimes

short lowercase

'a, 'de, 'static

Constants and statics

SCREAMING_SNAKE_CASE

LOAN_DAYS

Features

no placeholder words such as use- or with-

serde, not with-serde

Acronyms count as one word in UpperCamelCase (Uuid, HttpClient, not UUID or HTTPClient). Conversion methods follow a cost convention: as_ is free and borrowed (as_str), to_ is expensive or allocates (to_string), and into_ consumes self (into_bytes). Getters do not use a get_ prefix (len(), not get_len()), and iterator methods are named iter, iter_mut and into_iter.

The Rust Style Guide and rustfmt

The official Rust Style Guide defines the default formatting, and rustfmt (run as cargo fmt) applies it. The most visible rules:

  • four spaces of indentation, no tabs; a maximum width of 100 characters;

  • opening braces on the same line as the item (fn main() {), trailing commas in multi-line lists;

  • where clauses, long function signatures and method chains broken one element per line;

  • imports sorted and grouped; one blank line at most between items.

cargo fmt              # format the whole package in place
cargo fmt --check      # CI: exit with an error if anything would change

A project can tune rustfmt with a rustfmt.toml, but most projects keep the defaults so that all Rust code looks the same. Since the 2024 edition, the style edition is versioned separately: style_edition = "2024" (the default for 2024 crates) changes, among other things, how version-sorted identifiers are ordered in imports. Details are in Tooling: Clippy, rustfmt & rust-analyzer.