The Standard Prelude
Understand how Rust automatically imports essential types, traits, and macros via the standard prelude, its contents across editions, and when to use no_implicit_prelude or no_std.
What Is the Prelude?
Every Rust module starts with a set of names already in scope — no use required. You can write let v = Vec::new(), return a Result, or call clone() on a value without ever importing those items. That set is the prelude.
The prelude is not a module you add to your code. It is an implicit collection of standard library items that the compiler brings into scope for every module in a crate. It covers traits, types, functions, and macros that appear in almost all Rust code.
Not a module member:
Prelude names are in scope, but they are not members of the current module. You cannot refer to self::Box or crate::Box even though Box works everywhere. The prelude is queried during name resolution, not by making items a real part of the module.
Why the Prelude Exists
Rust programs use many standard types and traits repeatedly. Without the prelude, you would have to write something like this for nearly every file:
use std::boxed::Box;
use std::option::Option::{self, Some, None};
use std::result::Result::{self, Ok, Err};
use std::vec::Vec;
use std::clone::Clone;
use std::cmp::PartialEq;
// ... and many more
That verbosity adds noise without real benefit — these are items you reach for constantly. At the same time, importing everything from the standard library would fill the scope with unused names and make code harder to reason about.
The prelude strikes a balance: it automatically imports a small, carefully chosen set of items that nearly every Rust program needs. The rest of the standard library stays explicit.
How the Prelude Works
Name resolution in Rust checks several places, in order: the current scope, any use imports, and then the prelude. When you write Vec, the compiler first looks in the local module. If it does not find Vec there or in an explicit use, it checks the prelude and resolves it to std::vec::Vec.
This implicit lookup means you never need to import Vec, but you can still shadow it if you define your own Vec in a module. The local binding takes priority.
Under the hood, each crate has a standard library prelude that depends on the crate’s edition and on whether the no_std attribute is present. For a normal binary or library with the standard library linked, the prelude corresponds to one of these modules:
std::prelude::v1for Rust 2015 and 2018std::prelude::rust_2021for Rust 2021std::prelude::rust_2024for Rust 2024
The compiler conceptually injects use std::prelude::rust_2021::*; into every module of a crate using edition 2021 (and similarly for other editions). That wildcard import is not something you write and cannot be overridden — it is applied automatically by the compiler.
Prelude imports are per module, not global:
The prelude is re‑applied to each module, including submodules. If you put #![no_implicit_prelude] on a module, it affects only that module and its descendants; other modules in the same crate still get the standard prelude.
Standard Prelude Contents Across Editions
The items in the prelude have grown slowly across editions to include new traits that became essential. The core set has remained stable; later editions add extra items that had become de facto standard in the Rust ecosystem.
Edition 2024 builds on the 2021 prelude by also including:
Future— the core trait for asynchronous programming. Asasync/.awaitbecame ubiquitous, requiring explicit imports for trait bounds and async combinators was needless ceremony.IntoFuture— the conversion trait that powers.awaiton types that can become aFuture(for example, converting aJoinHandleinto a future).
You can inspect the exact items for your edition by looking at the corresponding module in the standard library documentation: std::prelude::rust_2021, etc. The compiler chooses the right module automatically based on your Cargo.toml edition key.
Never import these items manually:
Because the prelude brings them into scope, adding use std::vec::Vec; or use std::option::Option; is harmless but unnecessary. The compiler may even issue a warning about an unused import if you do it redundantly.
Other Preludes in the Standard Library and Ecosystem
The automatic prelude is just one instance of a common Rust pattern. Some standard library modules define their own manual preludes that you import explicitly:
use std::io::prelude::*;
This brings in Read, Write, BufRead, and Seek — traits that are essential when working with I/O, but not needed in every single module. A single glob import pulls them all in, which is far more ergonomic than five separate use lines.
Library crates frequently follow the same convention. They expose a prelude module that re‑exports the most commonly used types and traits:
// Inside a library crate: src/prelude.rs
pub use crate::client::Client;
pub use crate::error::Error;
pub use crate::config::Config;
A downstream user then imports everything with one line:
use my_lib::prelude::*;
This is entirely opt‑in. Unlike the standard prelude, these manual preludes are never injected automatically.
Disabling the Prelude
There are rare situations where you want complete control over what names enter scope — for example, in no_std embedded code or in a crate where every name must be explicitly justified. The #![no_implicit_prelude] attribute removes the standard prelude, the extern prelude, the macro_use prelude, and the tool prelude from the annotated module and its descendants.
#![no_implicit_prelude]
fn main() {
let v = Vec::new(); // error[E0433]: failed to resolve: use of undeclared type `Vec`
}
Everything disappears:
With no_implicit_prelude, not only Vec and Option vanish — even Result, Ok, Err, String, println!, and format! are gone unless you import them manually.
To fix the example, import exactly what you need:
#![no_implicit_prelude]
use std::vec::Vec;
fn main() {
let v = Vec::new();
// But println! and other macros are still missing unless you add them.
}
The language prelude — which provides built‑in types like bool, char, str, integer types, and floating‑point types — is not affected by no_implicit_prelude. Those primitives are always available.
A small set of macros (such as panic!, compile_error!, concat!, stringify!, and format_args!) currently remain in scope even with #![no_implicit_prelude]. This is a known compiler behaviour that may change in future editions.
Do not rely on implicit macros after no_implicit_prelude:
While assert! and unreachable! often work today when the attribute is active, the compiler team has indicated this is not a stable guarantee. Always bring the macros you use into scope explicitly.
The Prelude and no_std
When you apply #![no_std] at the crate root, the standard library is not linked automatically. The standard prelude then switches from std::prelude::rust_20XX to core::prelude::rust_20XX. The core prelude includes only items that work without an operating system or allocator — it omits String, Vec, Box, and other heap‑dependent types, because core itself does not support allocation.
If you later add an allocator (via the alloc crate), you still need to import Vec, Box, and String explicitly — they are not part of the core prelude.
#![no_std]
extern crate alloc;
use alloc::vec::Vec; // required; not in core prelude
use alloc::boxed::Box; // required
This ensures that embedded and kernel developers never accidentally pull in heap‑allocated types.
Common Misconceptions and Pitfalls
Assuming every standard library type is in the prelude.
HashMap, BTreeMap, PathBuf, Mutex, and many other commonly used types are not in the prelude. You import them explicitly. The prelude is minimal by design.
Trying to access prelude items through paths.
self::Box will not compile. The prelude does not make Box a child of your module; it only makes the name available for direct use.
Manually importing the prelude.
Writing use std::prelude::v1::*; is both redundant and confusing. It can shadow your own items in surprising ways and may cause unused_imports warnings when combined with the automatic injection.
Thinking no_implicit_prelude removes all built‑in names.
Primitive types and the language prelude always remain. You can still write let x: i32 = 0; even with the attribute active.
Relying on prelude items in macros without $crate paths.
If you write a macro that uses Vec, it will work inside any module where the standard prelude is present. But a user who applies #![no_implicit_prelude] might get a compile error inside your macro invocation. Hygienic macros should use absolute paths like $crate::vec::Vec or ::std::vec::Vec when exporting code that needs these types.
Summary
The standard prelude is Rust’s answer to a constant tension: importing the same few essentials over and over is tedious, but importing everything is messy. It solves this by automatically bringing a lean set of traits, types, and macros into every module — Option, Result, Vec, Clone, Into, and the rest. This list expands only when a trait or type truly becomes a building block of everyday Rust, as TryFrom did in 2021 and Future did in 2024.
The prelude is not magical. It is an implicit use of a specific module chosen by your crate’s edition, applied per module and removable via #![no_implicit_prelude]. Other preludes, like std::io::prelude, follow the same pattern but require an explicit import — they are a library author’s tool for grouping commonly used items without polluting the global namespace.
When you understand exactly what the prelude provides, you write less boilerplate, avoid redundant imports, and know precisely where to look when a familiar name suddenly goes missing. This clarity is essential before moving to larger organizational structures.