Skip to content
Learn Rust
Theme

A tour of the standard library

Rust’s standard library, std, is smaller than Go’s or Python’s, on purpose. It has the building blocks every program needs: collections, files, text, threads, processes, time. Everything else comes from crates. This page shows what’s in the box, what isn’t, and how to read the docs without getting lost.

RememberAs a rule of thumb: if it touches the operating system or nearly every program needs it, it’s in std. If it might need to change, it’s a crate.

These are the modules and types you’ll actually use. Each row says what you reach for, what it is for, and which lesson teaches it. Under the table, the closest thing in your language: names only, I did not run those programs. I did check that each name that ships with a language exists, by compiling or importing it in the image behind this site (Go 1.27, Python 3.13, Node 24, Java 25, Kotlin 2.4, .NET 10, GCC 14).

You want to Reach for What it is for Lesson
A list Vec an ordered list that grows Vectors
Look up by key HashMap, BTreeMap a value found by key; BTreeMap keeps the keys sorted Maps
Sets, queues, heaps HashSet, VecDeque, BinaryHeap “have I seen it?”, a queue with two ends, the biggest item next Collections
Files fs::read_to_string, File, BufReader read or write a whole file in one call, or line by line Files and I/O
File paths Path, PathBuf join and take apart paths, which are not always valid text Paths
Text str and String methods: split, trim, parse cut text apart, search it, turn it into numbers Working with text
Printing format!, write!, Display build text from values; make your own types printable Printing and formatting
Arguments, environment env::args, env::var the command line as raw strings (flag parsing is a crate), and environment variables Arguments
Other programs process::Command, ExitCode start a program, capture its output, read how it ended Running other programs
Time Instant, SystemTime, Duration measure elapsed time, read the wall clock, hold an amount of time Time
Threads, locks, channels thread::spawn, Arc, Mutex, mpsc::channel run work in parallel, share data safely, pass messages Threads, Shared state, Channels
Sockets net::TcpListener, TcpStream, UdpSocket raw TCP and UDP no lesson here: std::net
Loops over anything Iterator map, filter, sum on every collection Iterators
Maybe a value, maybe an error Option, Result, ? say “nothing” or “failed” in the type Option, Errors
Owning on the heap, sharing Box, Rc, RefCell one heap owner, shared ownership, checking borrows at run time Box, Rc and RefCell

Rust’s std has no calendar dates or time zones (see the next section). Two lessons go beyond the rows: Strings is what String and &str are, and Macros, attributes and naming owns the {} format-string syntax.

What’s not in the box (and where it lives)

Section titled “What’s not in the box (and where it lives)”

Go ships JSON, HTTP and regular expressions in its standard library, and so do several other languages. Rust ships none of them. A name without “(library)” or “(POSIX)” below ships with the language or its runtime, and I checked that it exists, as above. For the libraries I only checked that the name exists (on npm, PyPI, Maven Central, NuGet or the project’s home page; The crates everyone uses says how). I ran none of them.

Every crate on the Rust side is one cargo add away. See Using other people’s code, and The crates everyone uses for the full list. Rust’s own std does have a random module, but the 1.99.1 docs mark it nightly-only, so rand it is.

Other languages made different bets. Anything stabilised in Rust’s std has to keep working: the project’s 2014 post on how Rust ships describes stable APIs as “a commitment to avoid breaking code that uses those features or APIs”. That is fine for Vec, and costly for things that are still changing.

The rand crate has had four incompatible series in a row (0.7, 0.8, 0.9 and 0.10; in 0.x versions Cargo treats each minor number as a breaking change). Async runtimes compete on different trade-offs. HTTP went from 1.1 to 2 to 3. Kept in crates, each can ship a better version without breaking anyone, and you choose when to upgrade.

The cost is that you pick crates yourself. The crates everyone uses lists the usual choice for each job, with the caveat that “usual” is a judgement. regex is a good example of the line: it is maintained by the Rust project’s regex team (a subteam of libs, per rust-lang/team), but it is a separate crate, not part of std.

Every Rust file starts with a short list of names already imported, called the prelude. That’s why Vec, String, Option, Some, None, Result, Ok, Err and Box just work, along with traits like Clone, Iterator and ToString. Everything else needs a use line, including HashMap. The prelude docs say it is “kept as small as possible”.

That catches people out: Vec is right there, but its sibling HashMap isn’t. See the gotcha at the bottom.

The docs at doc.rust-lang.org/std are the reference, and the signatures look dense at first. Once you can read them, a signature tells you a lot, including things that other languages’ docs say only in prose. If the rust-docs component is installed (the default rustup profile includes it), rustup doc --std opens an offline copy.

Here are three real signatures from std (as the 1.99.1 docs show them), and what each piece means:

Rustfrom the std docs
// std::fs
pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> // the io::Result alias
// str (the methods on every &str and String)
pub fn trim(&self) -> &str
// HashMap<K, V>
pub fn get<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
When you see It means
&self Only reads the value. You can call it any number of times, and others can read at the same time.
&mut self Changes the value. Nobody else can look while it runs.
self Takes the value. You can’t use it afterwards (unless it’s Copy).
-> Option<T> Might give back nothing. You must handle None.
-> Result<T, E> or io::Result<T> Might fail. Use ?, match or unwrap_or.
-> &str from &self A view into the input, not a copy. It can’t outlive the input.
-> String, -> Vec<T> New memory you own.
<P: AsRef<Path>> or impl AsRef<Path> Accepts any type that can be looked at as a path: &str, String, PathBuf.
where K: Borrow<Q> A map with String keys can be searched with a plain &str. No temporary String needed.
Q: ... + ?Sized Also works for types like str whose size isn’t known. Safe to ignore while reading.

The closest thing in your language, for the first seven rows:

Here are the three in action: one function taking three kinds of path, a borrowed trim, and a String-keyed map searched with &str.

Rustsrc/main.rs
use std::collections::HashMap;
use std::fs;
use std::path::Path;
// impl AsRef<Path> means "anything that can be looked at as a path"
fn size_of(path: impl AsRef<Path>) -> std::io::Result<u64> {
Ok(fs::metadata(path)?.len())
}
fn main() -> std::io::Result<()> {
let file = std::env::temp_dir().join("rbc-tour.txt");
fs::write(&file, "twelve bytes")?;
// one function, three kinds of path: &PathBuf, &String, &str
let as_text: String = file.to_string_lossy().into_owned();
println!(
"{} {} {}",
size_of(&file)?,
size_of(&as_text)?,
size_of(as_text.as_str())?
);
// trim(&self) -> &str: the result borrows from the input, nothing is copied
let padded = String::from(" hi ");
let trimmed: &str = padded.trim();
println!("[{trimmed}]");
// HashMap<String, _>::get accepts a &str and returns Option<&V>
let mut ages: HashMap<String, u32> = HashMap::new();
ages.insert("ana".to_string(), 30);
println!("{:?} {:?}", ages.get("ana"), ages.get("zed"));
fs::remove_file(&file)?;
Ok(())
}
$ cargo run
12 12 12
[hi]
Some(30) None

On a type’s docs page, scroll past its own methods to Trait Implementations. That’s where many of the useful methods are hiding. File has very few methods of its own. Reading and writing come from impl Read for File and impl Write for File.

The catch: a trait’s methods only work after you import the trait. The Files and I/O gotcha is exactly this. These are the traits you’ll meet constantly:

Trait What it gives you Need a use?
Display, Debug {} and {:?} printing, and .to_string() only to implement them (use std::fmt)
Iterator map, filter, sum, collect and dozens more no, it’s in the prelude
Read, Write, BufRead read_to_string, write_all, writeln!, lines() yes: use std::io::{Read, Write, BufRead}
FromStr "42".parse::<u32>() no for parse; yes for i32::from_str("42") (use std::str::FromStr)
From, Into conversions, and how ? converts one error type into another no
Default Type::default(), an “empty” value no
Clone, Copy .clone(), and copy-instead-of-move for small types no
PartialEq, Eq, Ord ==, sorting no, usually derived
Hash using a type as a HashMap key #[derive(Hash)] needs no import; writing T: Hash as a bound does (use std::hash::Hash)

std is actually built from two smaller libraries. core is everything that needs no memory allocation and no operating system: Option, Result, iterators, numbers. alloc adds heap types: Box, Vec, String. std adds the operating system: files, threads, networking, Instant and SystemTime. HashMap is in std too.

You’ll only notice this when you write for a microcontroller or a kernel, where you say #![no_std] and keep just core (and maybe alloc). The same Option and iterators work there.

Gotcha

Using HashMap like you use Vec

Looks fine: in Go it runs fine. Rust won't compile it.

Works in Go. In Rust it's just a rule.No direct equivalent in Kotlin. Just a Rust rule.Not run in Python on this page.Not run in TypeScript on this page.Not run in Java on this page.Not run in C# on this page.Not run in C on this page.Not run in C++ on this page.
Rustsrc/main.rs✗ won't compile
fn main() {
let names = vec!["ana", "bo"]; // Vec: always there
let mut ages = HashMap::new(); // HashMap: not imported
ages.insert("ana", 30);
println!("{} {}", names.len(), ages.len());
}
package main
import "fmt"
func main() {
names := []string{"ana", "bo"} // slices: built in
ages := map[string]int{} // maps: built in too
ages["ana"] = 30
fmt.Println(len(names), len(ages))
}
$ go run main.go✓ runs
2 1
Kotlinno equivalent

Kotlin imports its collections for you, so mutableMapOf and HashMap work with no import.

There is no Python version of this one.

There is no TypeScript version of this one.

There is no Java version of this one.

There is no C# version of this one.

There is no C version of this one.

There is no C++ version of this one.

What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0433?
error[E0433]: cannot find type `HashMap` in this scope
 --> src/main.rs:3:20
  |
3 |     let mut ages = HashMap::new(); // HashMap: not imported
  |                    ^^^^^^^ use of undeclared type `HashMap`
  |
help: consider importing this struct
  |
1 + use std::collections::HashMap;
What happens in Go

In Go, maps are part of the language, like slices. Nothing to import.

What happens in Kotlin

Kotlin imports its collections for you, so mutableMapOf and HashMap work with no import.

Why it looks fine

In Go, maps are part of the language, like slices. Nothing to import.

Why it looks fine

In Go, maps are part of the language, like slices. Nothing to import.

Why it looks fine

In Go, maps are part of the language, like slices. Nothing to import.

Why it looks fine

In Go, maps are part of the language, like slices. Nothing to import.

Why it looks fine

In Go, maps are part of the language, like slices. Nothing to import.

Why it looks fine

In Go, maps are part of the language, like slices. Nothing to import.

What Rust sees

Vec is in the prelude, so it's always there. HashMap lives in std::collections and needs a use line.

Why Rust says no

The prelude is kept short on purpose, so your own names rarely clash with std's. Vec and String made the cut because nearly every file uses them. Maps are common, but not that common. The error message tells you the exact line to add.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
use std::collections::HashMap;
fn main() {
let names = vec!["ana", "bo"];
let mut ages = HashMap::new();
ages.insert("ana", 30);
println!("{} {}", names.len(), ages.len());
}
$ cargo run
2 1