Anatomy of a Rust file
Rust is a systems programming language focused on safety, speed, and concurrency, with a strong emphasis on memory safety without a garbage collector.
File extensions: .rs
Every part of the example below is labelled and explained. This page is one of 55 annotated tours on AnatomyOf, a free, open-source project by LunarWerx Studios.
What is inside a Rust file
Comment
Single-line (//) or block (/* ... */), ignored by the compiler.
A double slash starts a comment that runs to the end of the line. A block comment opens with a slash-star and closes with a star-slash, and unlike C or Go, Rust block comments are allowed to nest inside one another. A comment written with three slashes becomes a doc comment: tools like rustdoc collect it and render it as documentation for the item immediately below it, and doctests inside a doc comment are compiled and run by cargo test.
Use declaration
Brings symbols from modules into scope with use.
A use declaration lets you refer to an item by a short name instead of its full path. use std::fmt brings the fmt module itself into scope, while a path like use std::collections::HashMap brings a single type in directly; braces group several imports from the same path on one line. The standard library, third-party crates pulled in through Cargo.toml, and your own modules are all reached the same way. An unused import produces a compiler warning, not an error, which is why cargo fix can safely prune them automatically.
Struct definition
Defines a custom data structure with named fields.
A struct groups related values into a single named type. The common form shown here, with named fields inside braces, is the most frequent shape, but Rust also supports tuple structs with positional fields and unit structs with no fields at all, often used as markers. A struct only describes data; behavior is added separately in one or more impl blocks. By default a struct and its fields are private to the module that defines them, and each must be marked pub individually to be visible from outside.
Field
A component of a struct with a specific type.
Each field pairs a name with a type, such as x: i32 for a signed 32-bit integer. Rust requires every field to have an explicit type annotation since the compiler does not infer struct shapes the way it infers local variable types. Fields are accessed with dot notation, like point.x, and can only be reassigned if the binding that owns the struct was declared mut. Rust has no field-level default values in the struct definition itself; construction must supply every field, though the ..Default::default() syntax can fill in the rest from a default instance.
Impl block
Defines methods and associated functions for a type.
An impl block attaches functions to a type without editing the type's own definition. A function inside the block that takes no self parameter, like new, is called an associated function and is invoked with the double-colon path Point::new; a function that takes self, &self, or &mut self is a method and is invoked with dot notation instead. A single type can have several impl blocks, and traits are implemented separately with impl TraitName for Type, which is how Rust achieves shared behavior across unrelated types without classical inheritance.
Function/method definition
Defines a reusable block of code with fn.
The fn keyword introduces a function. Parameters are written name: Type, and the return type follows an arrow, as in fn new(x: i32, y: i32) -> Point; a function with no arrow implicitly returns the unit type. The final expression in the body is returned automatically when it has no trailing semicolon, so return is often optional. A method's first parameter describes how it borrows the instance: &self borrows it immutably, &mut self borrows it mutably, and a bare self takes ownership and consumes the value. Which form a method uses determines what the caller is allowed to do with the value afte
Main function
The entry point for an executable.
Every Rust binary begins execution in a function named main, which takes no arguments in its simplest form and returns nothing. The compiler enforces that exactly one main function exists in the crate that produces the executable; library crates do not need one at all. main can also return a Result, typically Result<(), Box<dyn Error>>, in which case returning Err prints the error with the Debug trait and exits with a nonzero status, which is a convenient way to use the question-mark operator for error handling directly at the top level.
Variable declaration (let)
let for immutable, let mut for mutable.
let binds a value to a name. Bindings are immutable by default, so let p1 = Point::new(10, 20) cannot be reassigned or have its fields changed later; adding mut, as in let mut p2, opts back into mutation for that one binding. This default is deliberate: it means a reader never has to search a function for reassignments to know a value is stable, and the compiler catches accidental mutation at compile time instead of it surfacing as a runtime bug. A new let with the same name can also shadow an earlier binding, which is different from mutating it, since the old value and the new one can even ha
Macro call (!)
Invokes a macro (code generator), ending with !.
A trailing exclamation mark marks a macro invocation, such as println!, distinguishing it from an ordinary function call. Macros like println! and format! expand at compile time into code that validates the format string against the arguments supplied, catching mismatched placeholders before the program ever runs. Because macros operate on syntax rather than on already-typed values, they can accept a variable number of arguments and generate code a regular function could not, such as vec![1, 2, 3] expanding into the calls needed to build and populate a Vec.
Control flow (conditional & loop)
Expressions (if/else) and loops (for, while, loop).
An if is an expression in Rust, not just a statement, so let status = if p1.x > p2.x { "p1 is further" } else { "p2 is further" } assigns whichever branch runs; both branches must produce the same type, and an else is required whenever the result is used as a value. Conditions need no surrounding parentheses, but the branch bodies always need braces. for i in 0..3 iterates a range where the upper bound is exclusive; 0..=3 would include it. while repeats as long as a condition holds, and the bare loop keyword repeats forever until a break, which can itself carry a value out of the loop as its r
Ownership & borrowing
Rust tracks who owns a value and enforces borrowing rules at compile time.
Every value in Rust has exactly one owner, and when that owner goes out of scope the value is dropped automatically, which is how Rust frees memory deterministically without a garbage collector. Passing a non-Copy value by itself, rather than by reference, moves ownership to the callee, and the original binding can no longer be used afterward. A reference such as &self borrows a value instead of taking it, and the borrow checker enforces that a value has either any number of immutable borrows or exactly one mutable borrow at a time, never both. This rule, checked entirely at compile time, is w
Official Rust site · All languages on AnatomyOf
The interactive tour needs JavaScript. Enable it to hover a callout and trace it into the code.