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.
What’s in the box
Section titled “What’s in the box”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.
Why is it so small?
Section titled “Why is it so small?”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.
What you get without importing anything
Section titled “What you get without importing anything”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.
Reading a function signature
Section titled “Reading a function signature”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:
// std::fspub 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.
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(())}12 12 12 [hi] Some(30) None
Traits do half the work
Section titled “Traits do half the work”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) |
Three layers: core, alloc, std
Section titled “Three layers: core, alloc, std”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.
The lessons in this track
Section titled “The lessons in this track”- Files and I/O: whole files, line by line, paths, and errors you can inspect.
- Collections:
HashSet,BTreeMap,VecDequeandBinaryHeap, and when to use which. - Working with text: splitting, trimming and parsing, and why case and Unicode comparisons are harder than they look.
- Arguments, environment, time and processes: talking to the world outside your program.
- Printing and formatting: what is behind the
{}:DisplayversusDebug, theFormatter, and why width is your job. The format-string syntax itself is in Macros, attributes and naming.
Read more
Section titled “Read more”- The
stddocs and the list of all items, for checking whetherstdhas something before you look for a crate. std::prelude: what is imported for you.- Rust by Example: std library types.
- The Rust Book, chapter 7.4: the
usekeyword.
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.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))}2 1
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.
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;
In Go, maps are part of the language, like slices. Nothing to import.
Kotlin imports its collections for you, so mutableMapOf and HashMap work with no import.
In Go, maps are part of the language, like slices. Nothing to import.
In Go, maps are part of the language, like slices. Nothing to import.
In Go, maps are part of the language, like slices. Nothing to import.
In Go, maps are part of the language, like slices. Nothing to import.
In Go, maps are part of the language, like slices. Nothing to import.
In Go, maps are part of the language, like slices. Nothing to import.
Vec is in the prelude, so it's always there. HashMap lives in std::collections and needs a use line.
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.
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());}2 1
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.