Hello World and Program Structure

Importing Standard Library Items

While Rust's prelude automatically brings the most common types (Option, Result, Vec, String) into scope, the rest of the standard library (std) is organized into modules such as std::collections, std::io, std::fs, std::cmp, and std::mem. You can refer to these items either by their full module path or by bringing their names into local scope with a use declaration.

Full Path vs use Declaration

Every item in the standard library has an unambiguous path starting with std:::

use std::collections::HashMap;

fn main() {
    // 1. Using the short name `HashMap` brought into scope by `use`:
    let mut scores = HashMap::new();
    scores.insert("Blue", 10);

    // 2. Using a full `std::...` path directly without a `use` statement:
    let minimum = std::cmp::min(10, 25);

    println!("scores = {scores:?}, minimum = {minimum}");
}
scores = {"Blue": 10}, minimum = 10

A use declaration has zero runtime cost; it merely creates a lexical alias in the current module scope so you do not have to repeat std::collections:: on every line.

Commonly Imported Standard Library Modules

Module Path Key Types, Traits, and Functions
std::collections HashMap, HashSet, BTreeMap, BTreeSet, VecDeque, BinaryHeap
std::cmp min, max, Ordering (Less, Equal, Greater), Reverse
std::mem size_of, size_of_val, align_of, swap, replace, take
std::fmt Display, Debug, Formatter, Result, Write
std::io stdin, stdout, Read, Write, BufRead, BufReader, Error
std::fs File, read_to_string, write, create_dir_all, read_dir
std::path Path, PathBuf
std::env args, var, current_dir
std::time Duration, Instant, SystemTime
std::sync Arc, Mutex, RwLock, mpsc, OnceLock
std::thread spawn, sleep, current

use Syntax Variations

Rust provides several ways to group, alias, and scope use imports:

Syntax Example Effect
Single item import use std::collections::HashMap; Brings HashMap into scope
Nested paths ({...}) use std::cmp::{min, max, Ordering}; Imports multiple items from the same prefix in one line
self in nested path use std::io::{self, Write}; Imports both std::io (as io) and std::io::Write (as Write)
Aliasing with as use std::io::Result as IoResult; Imports the item under a different local name to avoid name collisions
Glob import (*) use std::io::prelude::*; Imports all public items from the target module
Local block use fn f() { use std::cmp::max; ... } Confines the import strictly to the enclosing block or function

Combining self and Nested Paths

When you want to call module-qualified functions like io::stdin() while also bringing a trait like io::Write into scope directly, combine self inside braces:

use std::collections::{BTreeMap, HashMap};
use std::mem::{self, size_of};

fn main() {
    let mut a = 1;
    let mut b = 2;

    // `mem` is in scope via `self`, and `size_of` is in scope directly:
    mem::swap(&mut a, &mut b);

    let _hash: HashMap<&str, i32> = HashMap::new();
    let _tree: BTreeMap<&str, i32> = BTreeMap::new();

    println!("a = {a}, b = {b}, size of i32 = {} bytes", size_of::<i32>());
}
a = 2, b = 1, size of i32 = 4 bytes

Resolving Name Collisions with Parent Modules or as

Both std::fmt and std::io define a type named Result. Importing both use std::fmt::Result; and use std::io::Result; in the same scope causes a duplicate-definition error (E0252).

Resolve collisions by either importing the parent modules (use std::fmt; use std::io; and writing fmt::Result / io::Result) or renaming one import with as:

use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;

fn check_types() -> (FmtResult, IoResult<()>) {
    (Ok(()), Ok(()))
}

fn main() {
    println!("{:?}", check_types());
}
(Ok(()), Ok(()))

Importing Traits to Unlock Their Methods

In Rust, you can only call a trait's methods on a type if that trait is currently in scope! For example, String implements std::fmt::Write (allowing write!(&mut my_string, ...)), and std::io::Stdout implements std::io::Write (allowing .flush()), but neither trait is in the default prelude.

If you call a trait method without importing the trait, rustc tells you the exact use statement to add:

// ❌ does not compile
fn main() {
    let mut buffer = String::new();
    write!(&mut buffer, "Value: {}", 42).unwrap();
}
error[E0599]: cannot write into `String`
 --> src/main.rs:3:5
  |
3 |     write!(&mut buffer, "Value: {}", 42).unwrap();
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `String` implements `std::fmt::Write`, which is required by `write!`, but it must be in scope
help: trait `Write` which provides `write_fmt` is implemented but not in scope; perhaps you want to import it
  |
1 + use std::fmt::Write;
  |

error: aborting due to 1 previous error

Adding use std::fmt::Write; brings the trait into scope and unlocks write! on String:

use std::fmt::Write;

fn main() {
    let mut buffer = String::new();
    write!(&mut buffer, "Value: {}", 42).unwrap();
    println!("{buffer}");
}
Value: 42

Tip: If you want to import a trait solely for its methods without polluting your namespace with the trait's name (such as Write), you can import it anonymously as _: use std::fmt::Write as _;.