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

P.1 · What the Prelude Imports and Why

Prologue — The Prelude and Implicit Imports Duration: ~15 minutes Library components: std::prelude::v1, std::prelude::rust_2021, std::prelude::rust_2024

Introduction

Many names are available in each Rust file without a use statement. Examples are println!, Vec, Option, String, and Clone, and there are dozens of other names.


These names come from the prelude. The prelude is a small set of items that Rust automatically imports into each module of each crate. The effect is the same as if each file started with this hidden line:

// The compiler adds this import to each module. The module name depends on the edition.
use std::prelude::rust_2024::*;

Knowledge of the prelude prevents a type of confusion that occurs even for experienced Rust developers: "Where does this trait come from? I did not import it." When you know the contents of the prelude, you know the source of these traits.


This tutorial shows:

  • The contents of the v1 prelude (the baseline for all editions)
  • The additions in the 2021 edition prelude
  • The additions in the 2024 edition prelude
  • The prelude traits that for loops, ?, collect(), and .await use

The Prelude Is an Implicit Use

The Rust compiler automatically adds the prelude import before it processes your file. The standard library documentation shows the full list for your edition under std::prelude.


The #![no_implicit_prelude] attribute fully removes the prelude from a specific file. Then dozens of usually implicit names are not in scope, and code that uses them does not compile.


The prelude is edition-dependent. When your Cargo.toml sets edition = "2024", you get the 2024 prelude. The 2024 prelude is a superset of the 2021 prelude, and the 2021 prelude is a superset of the v1 prelude.


Figure: Rust Prelude by Edition — Each Layer Inherits the One Below

The v1 Prelude: The Foundation

The v1 prelude (the prelude of the 2015 and 2018 editions) is the baseline.
Each item in it is available in each edition.
The subsections that follow show the primary categories.

Macros

The macros that Rust programs use most are prelude items:


  • println!, print!, eprintln!, eprint!: formatted output to stdout or stderr
  • vec!: a short form of Vec::new() and a sequence of push calls
  • format!: builds a String from a format template
  • assert!, assert_eq!, assert_ne!: runtime tests of invariants
  • dbg!: prints the expression and its value to stderr, and returns the value
  • todo!, unimplemented!, unreachable!, panic!: control flow markers

Types and Structs

  • Option<T> with Some(T) and None
  • Result<T, E> with Ok(T) and Err(E)
  • String: an owned, heap-allocated UTF-8 string
  • Vec<T>: a growable, heap-allocated array
  • Box<T>: a heap pointer with one owner

Traits

This category is the most important. Normal Rust syntax uses these traits:


  • Clone: the explicit .clone() method
  • Copy: an implicit bitwise copy on assignment
  • Drop: the destructor that runs when a value leaves scope
  • Iterator: the .map(), .filter(), .collect() chain
  • IntoIterator: the for x in collection { } syntax
  • From<T>: String::from("hello") and other conversions
  • Into<T>: "hello".into() (a blanket implementation from From)
  • PartialEq, Eq: the == and != operators
  • PartialOrd, Ord: the <, >, <=, >= comparisons
  • Send, Sync: marker traits for thread safety
  • Sized: an implicit bound on most generics

A for loop compiles without an import, and the trait that it uses is IntoIterator. The ? operator propagates an error automatically, and the trait that it uses is From. The operator calls From::from() on the error value. This call converts the value to the error type that the function returns.

Use case: What the v1 prelude enables, visualized

fn main() {
    // println!, eprintln!, print!, eprint!: write to stdout or stderr
    println!("=== Macros from the prelude ===");
    println!("println! works with no import");
    eprintln!("eprintln! also works — goes to stderr");

    // vec!: constructs a Vec<T> inline
    let numbers = vec![10, 20, 30, 40, 50];
    println!("vec!: {numbers:?}");

    // format!: constructs a String
    let greeting = format!("Hello, {}!", "prelude");
    println!("format!: {greeting:?}");

    // assert!, assert_eq!, assert_ne!: panic if the condition is false
    let computed = 2_i32 + 2;
    assert_eq!(computed, 4);
    assert_ne!(2 + 2, 5);
    assert!(greeting.contains("Hello"));
    println!("Assertions passed");

    // dbg!: prints the source location, the expression, and the value to stderr.
    // Then it returns the value.
    let doubled = dbg!(6 * 7);
    assert_eq!(doubled, 42);

    println!("\n=== Types from the prelude ===");

    // Option<T>: no import is necessary
    let maybe: Option<i32> = Some(99);
    let nothing: Option<i32> = None;
    println!("Option: maybe={maybe:?}, nothing={nothing:?}");
    assert!(maybe.is_some());
    assert!(nothing.is_none());

    // Result<T, E>: no import is necessary
    let ok: Result<i32, &str> = Ok(42);
    let err: Result<i32, &str> = Err("something went wrong");
    println!("Result: ok={ok:?}, err={err:?}");
    assert!(ok.is_ok());
    assert!(err.is_err());

    // String: an owned heap string, no import is necessary
    let owned_str = String::from("owned string");
    println!("String: {owned_str:?}");

    // Box<T>, Vec<T>: owned heap types, no import is necessary
    let boxed: Box<i32> = Box::new(100);
    let bytes: Vec<u8> = vec![1, 2, 3];
    println!("Box: {boxed:?}, Vec: {bytes:?}");

    println!("\n=== Traits from the prelude ===");

    // Clone: explicit duplication
    let original = String::from("hello");
    let cloned = original.clone(); // Clone is in scope, so the method call resolves
    assert_eq!(original, cloned);
    println!("Clone: original={original:?}, cloned={cloned:?}");

    // Copy: implicit bitwise duplication
    let orig: i32 = 7;
    let copy_of_orig = orig; // i32 is Copy, so `orig` is still valid
    assert_eq!(orig, copy_of_orig);
    println!("Copy: orig={orig}, copy_of_orig={copy_of_orig}");

    // Drop: runs automatically when a value leaves scope
    {
        struct Loud(i32);
        impl Drop for Loud {
            fn drop(&mut self) {
                println!("  Drop called for Loud({})", self.0);
            }
        }
        let _loud = Loud(1);
        println!("About to leave inner scope...");
    } // <- Drop::drop runs here automatically
    println!("Left inner scope");

    // Iterator: its methods map and sum are in scope without an import
    let sum: i32 = numbers.iter().map(|&n| n * 2).sum();
    println!("\nfor loop via IntoIterator (no import needed):");
    // IntoIterator: the `for` loop calls into_iter on `&numbers`
    for n in &numbers {
        print!("  {n}");
    }
    println!();
    println!("sum of doubled: {sum}");
    assert_eq!(sum, 300); // 10+20+30+40+50 = 150, doubled = 300

    // From<T> / Into<T>: infallible conversions
    let from_i32: i64 = i64::from(42_i32);    // From is in the prelude
    let owned_hello: String = "hello".into(); // Into, through the blanket implementation
    println!("\nFrom/Into: from_i32={from_i32}, owned_hello={owned_hello:?}");
    assert_eq!(from_i32, 42);
    assert_eq!(owned_hello, "hello");

    println!("\nAll assertions passed.");
}

00_01_prelude_basics.rs prints the lines below. The eprintln! line and the dbg! line go to stderr. The dbg! line shows the path and the position in the example file. The path depends on the directory from which you build the example:

=== Macros from the prelude ===
println! works with no import
eprintln! also works — goes to stderr   # (stderr)
vec!: [10, 20, 30, 40, 50]
format!: "Hello, prelude!"
Assertions passed
[prologue-00-the-prelude/examples/src/bin/00_01_prelude_basics.rs:33:19] 6 * 7 = 42   # (stderr, path varies)

=== Types from the prelude ===
Option: maybe=Some(99), nothing=None
Result: ok=Ok(42), err=Err("something went wrong")
String: "owned string"
Box: 100, Vec: [1, 2, 3]

=== Traits from the prelude ===
Clone: original="hello", cloned="hello"
Copy: orig=7, copy_of_orig=7
About to leave inner scope...
  Drop called for Loud(1)
Left inner scope

for loop via IntoIterator (no import needed):
  10  20  30  40  50
sum of doubled: 300

From/Into: from_i32=42, owned_hello="hello"

All assertions passed.

Examine the for loop. You write this loop:

// numbers: the Vec<i32> from the example above
for n in &numbers { ... }

The compiler desugars the loop to code that is almost the same as this:

// `&Vec<i32>` implements IntoIterator, and into_iter returns a slice iterator.
let mut iter = IntoIterator::into_iter(&numbers);
// next returns Some(&i32) for each element, and then None.
while let Some(n) = iter.next() { ... }

The compiler finds IntoIterator itself, so the loop does not need the trait name in scope. The prelude puts the same trait in scope for your code. Thus you can also write the desugared form, or call .into_iter(), without an import.

The 2021 Prelude: TryFrom, TryInto, FromIterator

Rust 2021 added three traits to the prelude. Before this edition, these traits were so common that a manual use statement for them was boilerplate in almost all code.

TryFrom and TryInto

Before edition 2021, you had to write use std::convert::TryFrom; before a call such as u8::try_from(some_value). In edition 2021 and later, TryFrom and TryInto are in the prelude.

Rust 1.34 stabilized the TryFrom and TryInto traits, but the prelude could include them only in a new edition. The traits introduced new method names (try_from, try_into), and these names could have conflicted with existing code.

Use case: Numeric narrowing and validated types without imports

// Converts an i64 to a u8, or returns the conversion error.
fn narrow_checked(v: i64) -> Result<u8, std::num::TryFromIntError> {
    let n: u8 = v.try_into()?; // on Err, ? returns the error to the caller
    Ok(n)
}

fn main() {
    println!("=== TryFrom (2021 prelude addition) ===");

    // In edition 2018 you had to write: use std::convert::TryFrom;
    // In edition 2021 and later, the trait is in scope.

    let big: i32 = 300;
    let result: Result<u8, _> = u8::try_from(big); // 300 is larger than u8::MAX (255)
    println!("u8::try_from(300i32) = {result:?}");
    assert!(result.is_err());

    let small: i32 = 42;
    let result: Result<u8, _> = u8::try_from(small); // 42 fits in a u8
    println!("u8::try_from(42i32)  = {result:?}");
    assert_eq!(result, Ok(42));

    // Signed narrowing: -1i32 cannot fit in u8
    let neg: i32 = -1;
    let result: Result<u8, _> = u8::try_from(neg);
    println!("u8::try_from(-1i32)  = {result:?}");
    assert!(result.is_err());

    println!("\n=== TryInto (2021 prelude addition) ===");

    // TryInto is the blanket counterpart of TryFrom.
    // The type annotation on the left selects the target type.
    let wide: i64 = 1_000_000;
    let narrow: Result<i16, _> = wide.try_into(); // i16::MAX is 32_767
    println!("1_000_000i64.try_into::<i16>() = {narrow:?}");
    assert!(narrow.is_err());

    let fine: i64 = 100;
    let narrow: Result<i16, _> = fine.try_into();
    println!("100i64.try_into::<i16>()       = {narrow:?}");
    assert_eq!(narrow, Ok(100i16));

    // The ? operator works with TryInto in functions that return Result.
    println!("narrow_checked(200) = {:?}", narrow_checked(200));
    println!("narrow_checked(999) = {:?}", narrow_checked(999)); // 999 does not fit in a u8
    assert!(narrow_checked(200).is_ok());
    assert!(narrow_checked(999).is_err());

    println!("\n=== FromIterator (2021 prelude addition) ===");

    // collect() is a method of Iterator, and FromIterator is its trait bound.
    // The type annotation selects the FromIterator implementation.

    // Collect into Vec<T>
    let squares: Vec<u32> = (1..=5).map(|n| n * n).collect();
    println!("squares: {squares:?}");
    assert_eq!(squares, [1, 4, 9, 16, 25]);

    // Collect into String: String implements FromIterator<char>
    let vowels: String = "hello world".chars().filter(|c| "aeiou".contains(*c)).collect();
    println!("vowels: {vowels:?}");
    assert_eq!(vowels, "eoo");

    // Collect into Result<Vec<T>, E>: stops at the first Err
    let inputs = ["1", "2", "3", "4"];
    let parsed: Result<Vec<i32>, _> = inputs.iter().map(|s| s.parse::<i32>()).collect();
    println!("parsed ok: {parsed:?}");
    assert_eq!(parsed, Ok(vec![1, 2, 3, 4]));

    let mixed = ["1", "oops", "3"]; // "oops" is not a number
    let parsed_err: Result<Vec<i32>, _> = mixed.iter().map(|s| s.parse::<i32>()).collect();
    println!("parsed err: {parsed_err:?}");
    assert!(parsed_err.is_err());

    // Collect into HashMap<K, V>: each (key, value) tuple becomes one entry
    let map: std::collections::HashMap<&str, usize> =
        ["alpha", "beta", "gamma"].iter().enumerate().map(|(i, s)| (*s, i)).collect();
    println!("map: {map:?}"); // the order of the entries changes between runs
    assert_eq!(map["alpha"], 0);
    assert_eq!(map["gamma"], 2);

    println!("\nAll assertions passed.");
}

00_02_prelude_2021.rs prints:

=== TryFrom (2021 prelude addition) ===
u8::try_from(300i32) = Err(TryFromIntError(PosOverflow))
u8::try_from(42i32)  = Ok(42)
u8::try_from(-1i32)  = Err(TryFromIntError(NegOverflow))

=== TryInto (2021 prelude addition) ===
1_000_000i64.try_into::<i16>() = Err(TryFromIntError(PosOverflow))
100i64.try_into::<i16>()       = Ok(100)
narrow_checked(200) = Ok(200)
narrow_checked(999) = Err(TryFromIntError(PosOverflow))

=== FromIterator (2021 prelude addition) ===
squares: [1, 4, 9, 16, 25]
vowels: "eoo"
parsed ok: Ok([1, 2, 3, 4])
parsed err: Err(ParseIntError { kind: InvalidDigit })
map: {"gamma": 2, "beta": 1, "alpha": 0}   # (order varies)

All assertions passed.

Why FromIterator needed a new edition

FromIterator is the trait that collect() uses to build its result. collect() is a method of Iterator, so it compiles in all editions without an import of FromIterator. Before edition 2021, code that used the trait name directly needed use std::iter::FromIterator;. Two examples are the call Vec::from_iter(iter) and a C: FromIterator<T> bound.

The 2021 prelude removed the need for this import. The change needed a new edition for the same reason as TryFrom and TryInto: the method name from_iter could conflict with existing code.

The 2024 Prelude: Future, IntoFuture

Rust 2024 adds two traits for async code. Async Rust matured, and Future became common in function signatures and trait bounds. Thus an explicit import was an unnecessary step.

Future

Future is the core trait of the async system of Rust. A Future<Output = T> represents an asynchronous computation that will produce a T at a later time. Each async fn returns an anonymous type that implements Future.

In editions before 2024, a function that accepts or names a Future in its signature needs use std::future::Future. In edition 2024, the trait is in scope automatically.

IntoFuture

IntoFuture is the trait that .await uses internally. When you write some_value.await, Rust calls IntoFuture::into_future(some_value) and then polls the result. Thus builders and other types can be directly awaitable, although they are not a Future themselves. This pattern is common in async database libraries and async HTTP libraries.

Use case: Future and IntoFuture in function signatures

use std::pin::Pin;
use std::task::Context;
use std::task::Poll;

// A minimal manual Future that completes on the first poll.
// The name `Future` is in scope without a `use` statement in edition 2024.
struct Ready<T>(Option<T>);

impl<T: Unpin> Future for Ready<T> {
    type Output = T;
    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<T> {
        // take() moves the value out of the Option and leaves None.
        Poll::Ready(self.0.take().expect("polled after completion"))
    }
}

// `impl Future<Output = i32>` in a signature needs no import in edition 2024.
fn make_ready(value: i32) -> impl Future<Output = i32> {
    Ready(Some(value))
}

// A builder type that becomes awaitable through IntoFuture.
struct FetchBuilder {
    url: String,
    timeout_ms: u64,
}

// The Future that a FetchBuilder becomes.
struct FetchFuture {
    url: String,
    timeout_ms: u64,
}

impl Future for FetchFuture {
    type Output = String;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<String> {
        // A simulated result: real code does async I/O here.
        Poll::Ready(format!("response from {} (timeout={}ms)", self.url, self.timeout_ms))
    }
}

impl IntoFuture for FetchBuilder {
    type Output = String;
    type IntoFuture = FetchFuture;

    // Moves the fields of the builder into the Future.
    fn into_future(self) -> FetchFuture {
        FetchFuture { url: self.url, timeout_ms: self.timeout_ms }
    }
}

fn main() {
    println!("=== Future (2024 prelude addition) ===");

    // This example has no async runtime, so it does not poll `fut`.
    let fut = make_ready(99);
    // The two lines print a size in bytes. A Ready<i32> has the size of an Option<i32>.
    println!("Created a Future: {}", std::mem::size_of_val(&fut));
    println!("Future size in bytes: {}", std::mem::size_of::<Ready<i32>>());
    assert_eq!(std::mem::size_of::<Ready<i32>>(), std::mem::size_of::<Option<i32>>());

    println!("\n=== IntoFuture (2024 prelude addition) ===");

    // FetchBuilder is not a Future, but it implements IntoFuture.
    // In async code you write: let response = builder.await;
    // This example calls into_future() directly:
    let builder = FetchBuilder { url: String::from("https://example.com"), timeout_ms: 5000 };
    let future = IntoFuture::into_future(builder); // future: FetchFuture
    println!("Created FetchFuture from FetchBuilder via IntoFuture");
    // On a 64-bit target: String (24 bytes) + u64 (8 bytes).
    println!("IntoFuture size: {} bytes", std::mem::size_of_val(&future));

    println!("\n=== What changes in edition 2024 ===");

    println!("Edition 2024 prelude summary:");
    println!("  Added to prelude: Future, IntoFuture");
    println!("  Inherited from 2021: TryFrom, TryInto, FromIterator");
    println!("  Inherited from v1: Option, Result, String, Vec, Clone, Copy,");
    println!("                     Drop, Iterator, IntoIterator, From, Into,");
    println!("                     println!, vec!, format!, assert!, ...");

    // The additions of all three prelude layers, with no `use` statement for them:
    let squares: Vec<u32> = (1_u32..=4).map(|n| n * n).collect(); // FromIterator (2021)
    let n: Result<u8, _> = u8::try_from(255_u32);                 // TryFrom (2021)
    let s: String = "hello".into();                               // Into (v1)

    assert_eq!(squares, [1, 4, 9, 16]);
    assert_eq!(n, Ok(255));
    assert_eq!(s, "hello");

    println!("\nAll assertions passed.");
}

00_03_prelude_2024.rs prints:

=== Future (2024 prelude addition) ===
Created a Future: 8
Future size in bytes: 8

=== IntoFuture (2024 prelude addition) ===
Created FetchFuture from FetchBuilder via IntoFuture
IntoFuture size: 32 bytes

=== What changes in edition 2024 ===
Edition 2024 prelude summary:
  Added to prelude: Future, IntoFuture
  Inherited from 2021: TryFrom, TryInto, FromIterator
  Inherited from v1: Option, Result, String, Vec, Clone, Copy,
                     Drop, Iterator, IntoIterator, From, Into,
                     println!, vec!, format!, assert!, ...

All assertions passed.

The "Mystery Trait" Problem

Figure: Diagnosing "Where Does This Trait Come From?"

Code such as this is one of the most common causes of confusion in Rust:

// This code has no `use` statement, but each line compiles.
// some_iterator: an iterator of i32 values. my_vec: a Vec.
let v: Vec<i32> = some_iterator.collect();
let s: String = "hello".into();
for item in my_vec { ... }
let x: u8 = 255_u32.try_into().unwrap(); // 255 fits in a u8, so unwrap does not panic

The prelude gives the explanation:

  • collect() works because Iterator is in the prelude (v1). It builds the Vec through FromIterator (in the prelude since 2021).
  • .into() works because Into is in the prelude (v1).
  • for uses IntoIterator, which is in the prelude (v1).
  • .try_into() works because TryInto is in the prelude (2021+).

When a method or a type has no visible source, examine std::prelude first.

Summary

Prelude contentsSince edition
Core types: Option, Result, String, Vec, Boxv1 (all editions)
Core traits: Clone, Copy, Drop, Send, Sync, Sizedv1
Conversion traits: From, Intov1
Iterator traits: Iterator, IntoIteratorv1
Comparison traits: PartialEq, Eq, PartialOrd, Ordv1
Macros: println!, vec!, format!, assert!, dbg!, todo!, …v1
Fallible conversions: TryFrom, TryInto2021
Collection building: FromIterator2021
Async primitives: Future, IntoFuture2024

In the remainder of this course, when a trait method or a type appears without a use statement, it comes from the prelude. This table shows the primary items, and std::prelude shows the full list.

Code Examples

FileDescription
00_01_prelude_basics.rsv1 prelude: macros, types, Clone, Copy, Drop, Iterator, From/Into
00_02_prelude_2021.rs2021 additions: TryFrom, TryInto, FromIterator without imports
00_03_prelude_2024.rs2024 additions: Future and IntoFuture, and all three layers together