Items, the Building Blocks of Rust

Understand Rust items - functions, structs, enums, modules, and more - and how they form the foundation of the module system and code organization.

When Rust code is compiled, the compiler does not think in terms of files. It thinks in terms of items — named, top‑level pieces of code that can be exported, hidden, re‑exported, and composed. A Rust module is nothing more than a collection of items, and the entire module tree from the crate root down is built from these building blocks.

A module is a container for items:

The Rust module system exists to group items into logical units. Every mod declaration creates a new item — a module item — that can in turn contain more items, forming the tree your project navigates with crate::, self::, and super:: paths.

What exactly is an item?

An item is any named, top‑level declaration that the Rust compiler recognises as a component of a crate. Items are the things you can write at module scope (directly inside a mod { } block or at the top of a .rs file). They have names (or at least identifiable slots, in the case of impl blocks and use declarations), and they are the only constructs that can carry a pub visibility modifier.

Expressions, local let bindings inside function bodies, and statements are not items. This distinction matters because the module system, privacy rules, and API design all operate at the item level.

If you are coming from languages where every file is automatically a namespace, Rust flips the mental model: you declare modules explicitly, and inside those modules you place items — the actual functions, types, constants, and other components that do the work.

The complete list of item kinds

Rust’s reference defines a closed set of constructs that are items. Here they are, grouped by what they do, so you can recognise them in any codebase:

CategoryItem kindsWhat it declares
FunctionsfnA named callable block of code.
Typesstruct, enum, union, trait, trait alias, type aliasA new data layout, enumeration, tagged union, shared behaviour contract, or a name for an existing type.
Constants and staticsconst, staticA compile‑time constant value or a fixed‑address global variable.
Implementationsimpl (inherent), impl Trait for Type (trait implementation)Adds methods to a type or fulfills a trait contract for a type.
ModulesmodA named container that can hold other items.
External linkageextern crate, extern blockDeclares a dependency on an external crate or an FFI binding to functions from another language.
Path shortcutsuseBrings a path into scope; with pub use it also re‑exports the path as part of the public API.

Macros are not items in this sense:

Declarative macros defined with macro_rules! are expanded very early during compilation and are not items the module system manages with pub in the same way. They do have their own export rules (#[macro_export]), but that is a separate mechanism from item visibility.

Every one of these can be placed inside a module or the crate root, and nearly all can be marked pub to control how they appear to parent modules and external consumers.

How items fit into the module tree

A crate starts with a root module — main.rs for a binary, lib.rs for a library. That root module is itself an implicit module item. From there, you declare child modules with mod, and inside every module you write items.

// Inside lib.rs (crate root)
mod network {
    // 'connect' is a function item inside the 'network' module
    pub fn connect(addr: &str) -> Result<TcpStream, std::io::Error> {
        TcpStream::connect(addr)
    }
    // 'Connection' is a struct item, but it's private to 'network'
    struct Connection {
        stream: TcpStream,
        active: bool,
    }
}
// Re-export the function item so it appears directly at the crate root
pub use network::connect;

In this snippet, network is a module item, connect is a function item, and Connection is a struct item. The pub use declaration is also an item — it makes the connect function available as crate::connect while keeping the internal network module private.

The privacy of each item follows a few strict rules:

  • All items are private by default. Only items marked pub are visible outside their containing module.
  • A child module can see all items (private and public) of its parent module. A parent module cannot see private items of its child modules.
  • Items at the same level (sibling modules) cannot see each other’s private items — they must be made pub or re‑exported.

Forgetting pub breaks compilation immediately:

If you write a function inside a module but forget pub, trying to call it from another module will produce a function is private compile error. Rust does not implicitly expose anything. This is a frequent source of confusion for newcomers who assume “it’s in a file, so it should be accessible.” The module declaration and visibility are all that count.

Functions as items

Functions are the most common item you’ll write. A function item consists of a signature (name, parameters, return type) and a body block. You can place generics, where clauses, and attributes like #[inline] or #[must_use] on a function item.

pub fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
    list.iter().reduce(|a, b| if a > b { a } else { b })
}

Visibility rules apply to the entire function. If the function is public, anyone who can reach the module can call it. The internals of the function (local variables, closures defined inside) are not separate items — they are owned by the function item.

Struct and enum items — field‑level privacy matters

Structs and enums are type items, but their internal privacy works differently.

Structs allow field‑level visibility. You can have a public struct with some private fields. This lets you expose a type while hiding its representation.

pub struct Breakfast {
    pub toast: String,        // anyone can read/write this
    seasonal_fruit: String,   // private: only the defining module and its children can access it
}
impl Breakfast {
    pub fn summer(toast: &str) -> Self {
        Breakfast {
            toast: String::from(toast),
            seasonal_fruit: String::from("peaches"),
        }
    }
}

Because seasonal_fruit is private, external code cannot create a Breakfast directly with struct literal syntax — it must go through the public constructor summer. This pattern is idiomatic for maintaining invariants.

Enums are all‑or‑nothing. If an enum is pub, all its variants are automatically public. There is no way to mark individual variants as private.

pub enum Appetizer {
    Soup,
    Salad,
}

This design choice keeps enums useful: if variants could be hidden, external code could not match exhaustively, defeating the purpose of using an enum to represent a finite set of alternatives.

Enum publicity can leak implementation details:

Because marking an enum pub exposes every variant, you should avoid adding internal variants (e.g., __Nonexhaustive) unless you are using #[non_exhaustive]. The #[non_exhaustive] attribute on an enum signals that new variants may be added in the future, preventing external code from matching exhaustively.

Constants and statics — values that live at module scope

const and static are items that give names to values stored outside function bodies.

Constants (const) are compile‑time evaluated and inlined at every use site. They are ideal for fixed configuration values or mathematical constants.

pub const MAX_CONNECTIONS: usize = 100;
pub const DEFAULT_HOST: &str = "localhost";

Statics (static) represent a single memory location for the entire lifetime of the program. They can be mutable (static mut), but accessing a mutable static requires an unsafe block because it can cause data races.

static REQUEST_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);

In practice, prefer const for immutable values and synchronisation primitives (like AtomicUsize) for global mutable state. Avoid static mut unless you are writing low‑level embedded code.

Trait and impl items — behaviour contracts and their fulfilment

A trait item defines a set of method signatures that types can implement. An impl item (an implementation) actually provides the method bodies for a specific type.

pub trait Summary {
    fn summarize(&self) -> String;
}
pub struct Article {
    pub headline: String,
    pub body: String,
}
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} — {}", self.headline, &self.body[..50.min(self.body.len())])
    }
}

Neither trait nor impl items carry their own pub modifier on the keyword itself (the trait or the methods inside can be pub). An impl block is accessible to external code if both the trait and the type being implemented are accessible. If the trait is public but the type is private, the impl is invisible outside the module.

Good API design with trait items:

A common pattern is to define a public trait in a library, implement it for a private type, and then return Box<dyn Trait> or impl Trait from a public function. The outside world can use the behaviour without ever seeing the concrete type. This is one of the most powerful uses of trait items as building blocks.

Modules as items — containers that are themselves items

A mod declaration creates a module item. This item acts as a namespace and can contain child items. Because a module is itself an item, it can be made pub to expose its entire contents to the parent scope (subject to the individual visibility of the items inside).

// The module item 'data' is private. Its contents are hidden from outside 'lib.rs'.
mod data {
    pub struct Cache { /* ... */ }   // Cache is public within 'data', but 'data' itself is not.
}
// The module item 'api' is public. External users can access what's inside.
pub mod api {
    pub fn query() { /* ... */ }
}

This layering means you can freely create deep module hierarchies for internal organisation without accidentally leaking implementation details — only the module items you mark pub contribute to your public API.

The use item — shortcuts and re‑exporting

A use declaration is an item that brings a path into scope so you can refer to it by its last segment. On its own it is a convenience. Add pub to it, and it becomes a re‑export — the path appears as if it were defined directly at the use location.

mod internal {
    pub fn detailed_calculation() -> f64 { 42.0 }
}
// Private shortcut for use inside this module only
use internal::detailed_calculation;
// Public re-export: external code can call crate::compute()
pub use internal::detailed_calculation as compute;

Re‑exporting with pub use is the primary mechanism for shaping a library’s public API without mirroring the internal directory structure. It allows you to keep a private module tree while presenting a flat, curated interface to users.

Common mistakes when working with items

Developers new to Rust often trip over the fact that not everything they write is an item, or misunderstand how visibility propagates through the item hierarchy.

let bindings are not items:

A let statement inside a function body is not an item. You cannot put pub on it, and it cannot be referenced from another module. Only items defined at module scope (or inside a mod { } block) participate in the module system.

impl blocks cannot be made pub directly:

You cannot write pub impl MyStruct { ... }. An impl block is accessible based on the accessibility of the type and any trait involved. If MyStruct is private, the methods in the impl block cannot be called from outside the module, even if you wrote pub fn inside them. The method’s pub only matters once the impl block itself is reachable.

enum variants have no separate visibility:

It is tempting to think you can hide one variant of a public enum while exposing others. You cannot. If you need to keep some variants internal, consider using a #[non_exhaustive] enum so external users must handle future variants, or wrap the enum inside a struct with a private field that restricts construction.

These pitfalls arise because items are the atoms of the privacy model. Once you internalise that everything at module level must be an item, and that privacy flows through the module tree item‑by‑item, the compiler errors become predictable.

The role of items in real‑world code organisation

Every production Rust project organises its code by deciding which items to expose and which to keep hidden. The module tree is just scaffolding — the real API design happens at the item level.

A library crate’s public API consists entirely of the items that are reachable through pub paths from the crate root. Functions, structs, enums, traits, type aliases, and constants — these are what your users will see in rustdoc output. The private items form the internal machinery: helper functions, intermediate data structures, module implementation details.

Designing a clean item interface often means:

  • Keeping struct fields private and providing constructors and accessor methods.
  • Using pub use to re‑export items from internal modules, so users never need to know the internal directory layout.
  • Splitting large impl blocks into multiple smaller ones across modules, using pub visibility on methods only where external use is genuinely needed.

Knowing items is half the battle:

Once you can look at a Rust source file and instantly identify which lines are items and which are not, you have a reliable mental model of where pub, mod, and use apply. If your code compiles without “cannot find” or “is private” errors, your items and their visibility are correctly wired into the module tree.

Summary — what to take away from this

Items are the nouns of Rust’s module grammar. Every function, type, constant, module, and re‑export you write is an item, and only items interact with the visibility system. Understanding that the module tree is a hierarchy of items — not files — unlocks the entire module system.

The most important single insight: privacy is per‑item, not per‑file. A Rust file can contain a mix of pub and private items, and the file’s role is only to hold the source text of the items declared by its parent module. There is no automatic connection between a file name and the module it contains — that connection is created by a mod declaration, which is itself an item.