A tour of the Rust standard library. This skill is a map, not a tutorial: it tells you which std module provides which capability, what the prelude auto-imports, how core / alloc / std are layered, and when to drop to no_std. Deep mechanics live in cross-referenced skills.
Cross-references: [[rust-impl-no-std]] [[rust-impl-concurrency]] [[rust-impl-channels]] [[rust-core-async-runtime]] [[rust-impl-async-tokio]]
When to use this skill
- User asks "is there a standard library type for X" or "do I need a crate for this"
- User asks "what does the prelude import", "what is
std::prelude::v1" - User asks "what is the difference between
core,alloc, andstd" - User asks "how do I make my crate
no_std" at the map level (mechanics live inrust-impl-no-std) - User confused about which
std::syncprimitive to pick at the catalogue level - User asks "which
std::collectionstype should I use" at the catalogue level - User asks "where is X in
std" and you need the module location
For deep mechanics of any module (lock poisoning, async runtimes, lifetime rules of &Path), refer to the cross-referenced skills.
Layering: core, alloc, std
std is built on two lower layers. ALWAYS understand which layer you depend on before writing portable code.
| Layer | What it provides | Requires |
|---|---|---|
core |
Primitives, traits, Option, Result, Iterator, formatting, atomics, Future, Pin. No allocation, no OS. |
Always available, including bare-metal no_std. |
alloc |
Box, Vec, String, Rc, Arc, BTreeMap, BTreeSet, VecDeque, BinaryHeap, LinkedList. |
A global allocator. Available on no_std with extern crate alloc;. |
std |
Everything in core and alloc plus OS-dependent: std::fs, std::io, std::net, std::process, std::thread, std::env, std::sync::Mutex, HashMap, HashSet. |
An operating system. |
ALWAYS note: HashMap and HashSet are in std, NOT in alloc, because they depend on RandomState for hash-DoS resilience which is seeded from the OS RNG. NEVER assume you can use HashMap on no_std; use the hashbrown crate directly with a manual hasher.
ALWAYS note: std::collections re-exports the alloc::collections types (BTreeMap, BTreeSet, VecDeque, BinaryHeap, LinkedList) plus the std-only types (HashMap, HashSet). Switching to no_std + alloc loses the hash-based types.
The prelude
The prelude is the set of names automatically imported into every module of every crate. Edition 2024 adds two items.
| Item | Prelude version |
|---|---|
Copy, Clone, Drop, Sized, Send, Sync, Unpin |
v1 (all editions) |
Option, Some, None, Result, Ok, Err |
v1 (all editions) |
Box, String, ToString, Vec, ToOwned |
v1 (all editions) |
Default, From, Into, TryFrom, TryInto |
v1 (all editions) |
Iterator, IntoIterator, DoubleEndedIterator, ExactSizeIterator, Extend |
v1 (all editions) |
AsRef, AsMut |
v1 (all editions) |
Debug, Eq, Hash, Ord, PartialEq, PartialOrd |
v1 (all editions) |
Fn, FnMut, FnOnce |
v1 (all editions) |
Future, IntoFuture |
edition 2024 prelude addition |
ALWAYS access the prelude as std::prelude::v1 (or core::prelude::v1 / alloc::prelude::v1 for the layered preludes). NEVER assume non-prelude items like std::collections::HashMap are auto-imported; you must use them.
Decision table: I need X to do Y, which std module
| I need to ... | Use this std module |
Key type or function |
|---|---|---|
| Store a growable list | std::vec |
Vec<T> |
| Look up by key (unordered) | std::collections |
HashMap<K, V> |
| Look up by key (sorted) | std::collections |
BTreeMap<K, V> |
| Queue / double-ended queue | std::collections |
VecDeque<T> |
| Set of unique values (unordered) | std::collections |
HashSet<T> |
| Set of unique values (sorted) | std::collections |
BTreeSet<T> |
| Max-heap priority queue | std::collections |
BinaryHeap<T> |
| Share data across threads | std::sync |
Arc<T> |
| Mutual exclusion lock | std::sync |
Mutex<T> |
| Multi-reader / single-writer lock | std::sync |
RwLock<T> |
| Lock-free counter / flag | std::sync::atomic |
AtomicUsize, AtomicBool, Ordering |
| One-time initialization (thread-safe, returns reference) | std::sync |
OnceLock<T> (since 1.70) |
| Lazy global (thread-safe, holds closure) | std::sync |
LazyLock<T, F> (since 1.80) |
| Wait for N threads | std::sync |
Barrier |
| Condition variable | std::sync |
Condvar |
| Read or write a file | std::fs + std::io |
File::open, File::create, fs::read_to_string, fs::write |
| Buffered I/O | std::io |
BufReader<R>, BufWriter<W> |
| Read stdin / write stdout | std::io |
io::stdin, io::stdout, io::stderr |
| Spawn an OS thread | std::thread |
thread::spawn, thread::scope (since 1.63) |
| Run a subprocess | std::process |
Command::new(...).output() |
| Sleep / measure elapsed time | std::time + std::thread |
thread::sleep, Instant::now, Duration::from_* |
| Wall-clock time | std::time |
SystemTime::now |
| Manipulate filesystem paths | std::path |
Path, PathBuf |
| Read environment variables / args | std::env |
env::args, env::var, env::vars, env::current_dir |
| FFI string with C (NUL-terminated) | std::ffi |
CString, CStr |
| FFI string with OS (platform-native) | std::ffi |
OsString, OsStr |
| Format a string | std::fmt |
format!, write!, Debug, Display |
| Pin a value | std::pin |
Pin<P>, pin! macro |
| Channel between threads | std::sync::mpsc |
channel, Sender, Receiver |
ALWAYS check this table before reaching for an external crate. NEVER add a dependency for capabilities that std already provides at acceptable quality.
std::collections at a glance
std::collections is the catalogue of generic containers. Selection rules:
- ALWAYS default to
Vec<T>for sequences. It is the most efficient choice for almost every use case. - ALWAYS prefer
HashMap<K, V>for unordered key-value lookup when keys implementHash + Eq. - ALWAYS use
BTreeMap<K, V>when you need ordered iteration, range queries, or deterministic iteration order (HashMap iteration order is intentionally randomised). - ALWAYS use
VecDeque<T>for FIFO queues;Vec::remove(0)is O(n),VecDeque::pop_frontis O(1). - ALWAYS use
BinaryHeap<T>for priority queues; it is a max-heap, usestd::cmp::Reversefor a min-heap. - NEVER reach for
LinkedList<T>; its only honest use case is constant-time splicing of large lists, which is almost never the bottleneck.VecorVecDequeis faster in practice due to cache locality.
HashMap and hash-DoS resilience
HashMap uses RandomState as its default hasher. RandomState is seeded from the OS RNG at creation time. This is a deliberate hash-DoS protection: an attacker who can choose keys cannot force pathological O(n) collisions in the table.
Consequences:
- ALWAYS expect iteration order to differ between runs and between insertions. NEVER rely on
HashMapiteration order for any logic. - For deterministic order, use
BTreeMapor wrap withIndexMap(external crateindexmap). - For maximum speed when keys are trusted (internal benchmark, fixed dataset), use
HashMapwith a non-random hasher such asFxHashMap(external craterustc-hash) orAHashMap(external crateahash). ALWAYS document the threat model when removing DoS protection. - The underlying implementation of
HashMapis thehashbrowncrate, which is the Swiss-table design used by ABSL. Inno_std + allocyou depend onhashbrowndirectly.
std::sync at a glance
std::sync collects the synchronization primitives.
Arc<T>: atomic reference count, thread-safe shared ownership. ALWAYS useArc, NEVERRc, when sharing across threads.Mutex<T>: exclusive lock. ReturnsLockResult<MutexGuard<T>>;Errindicates poisoning from a panic while locked. ALWAYS handle poisoning explicitly or call.unwrap()only when the panic is unrecoverable.RwLock<T>: many readers OR one writer. ALWAYS preferMutexfor write-heavy workloads;RwLockoverhead beatsMutexonly when reads dominate.atomic(AtomicUsize,AtomicBool,AtomicPtr,AtomicI32, etc.) : lock-free integer / pointer operations with explicitOrdering. ALWAYS pickOrdering::Relaxedfor counters with no cross-thread observation requirements;Ordering::Acquire/Ordering::Releasefor synchronization;Ordering::SeqCstonly when you have proven a need.Once: run a closure exactly once across threads. Legacy; ALWAYS preferOnceLockorLazyLockfor new code.OnceLock<T>(since 1.70) : one-time initialization that returns&T. Replaceslazy_staticfor non-closure cases.LazyLock<T, F>(since 1.80) :staticlazily initialized by a closure on first access. Replaceslazy_staticfor closure cases.Barrier: block N threads until all arrive.Condvar: condition variable, used withMutex<bool>orMutex<Queue>for "wait until predicate holds" patterns.mpsc(sub-module): multi-producer, single-consumer channel.channel()returns(Sender<T>, Receiver<T>);sync_channel(bound)for bounded backpressure.
NEVER use std::sync::Mutex inside async code (holding the guard across .await blocks the runtime worker). Use tokio::sync::Mutex or similar runtime-specific async locks. See [[rust-core-async-runtime]].
std::io at a glance
std::io defines the I/O traits and helpers. Almost all Read / Write consumers are generic over the trait, not concrete types.
| Item | Purpose |
|---|---|
Read |
read(&mut self, buf: &mut [u8]) -> io::Result<usize> |
Write |
write(&mut self, buf: &[u8]) -> io::Result<usize>, plus write_all, flush |
BufRead |
read_line, lines(), fill_buf |
Seek |
seek(SeekFrom) |
BufReader<R> |
Wraps a Read with an in-memory buffer; ALWAYS wrap raw File reads in BufReader unless you are doing one big read |
BufWriter<W> |
Wraps a Write with an in-memory buffer; flush on drop (errors are swallowed; flush explicitly to detect them) |
io::stdin(), io::stdout(), io::stderr() |
Handles to the standard streams; each acquires a lock per call (prefer .lock() for tight loops) |
io::Result<T> |
Alias for Result<T, io::Error> |
io::Error, io::ErrorKind |
Error type and discriminant; use ErrorKind::NotFound, WouldBlock, etc. for matching |
io::copy(&mut R, &mut W) |
Copy bytes between any Read and any Write |
ALWAYS check the return of Write::write: a partial write is legal. Use write_all to loop until done, or handle the partial yourself.
NEVER call stdout().write_all(...) in a tight loop without locking once via stdout().lock(); each stdout() call acquires the lock and is significantly slower.
std::fs and std::path at a glance
std::fs covers filesystem operations; std::path covers cross-platform path manipulation.
Key items:
File::open(path)opens read-only;File::create(path)opens write-only-truncate;OpenOptions::new().read(true).write(true).create(true).append(true).open(path)for full control.fs::read_to_string(path)reads an entire file into aString;fs::write(path, contents)writes a slice or string.fs::metadata(path)returnsMetadata(file size, mtime, permissions).fs::create_dir,fs::create_dir_all,fs::remove_file,fs::remove_dir,fs::remove_dir_all,fs::rename,fs::copy.fs::read_dir(path)returns an iterator ofio::Result<DirEntry>.
Path is the borrowed unsized type (analogous to &str); PathBuf is the owned growable type (analogous to String).
- ALWAYS accept
&Path(or genericallyimpl AsRef<Path>) in function parameters, NEVER&PathBuf. - ALWAYS use
Path::join,Path::with_extension,Path::file_name,Path::componentsfor manipulation; NEVER concatenate strings. - NEVER assume paths are valid UTF-8; on Unix they are arbitrary bytes, on Windows they are arbitrary UTF-16. Use
Path::display()for human output andOsStrfor byte-level work.
NEVER use blocking std::fs calls inside an async runtime (Tokio, async-std, smol); the runtime worker stalls. Use tokio::fs or wrap in tokio::task::spawn_blocking. See [[rust-impl-async-tokio]].
std::process at a glance
std::process::Command is the builder for spawning subprocesses.
use std::process::{Command, Stdio};
let output = Command::new("ls")
.arg("-la")
.arg("/tmp")
.env("LC_ALL", "C")
.stdout(Stdio::piped())
.output()?; // io::Result<Output>
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
Key API:
Command::new(program); chained.arg,.args,.env,.env_clear,.env_remove,.current_dir,.stdin,.stdout,.stderr..output()runs to completion and collectsOutput { status, stdout, stderr }..status()runs to completion and returns only the exit status..spawn()returnsChildfor streaming I/O; ALWAYS call.wait()or.wait_with_output()to reap the process.process::exit(code)terminates the current process without unwinding (Drop is NOT run).process::abort()aborts (no destructors, signal SIGABRT on Unix).
NEVER pass user-controlled strings through a shell. ALWAYS use Command::arg per argument; arguments are passed directly to the OS without shell interpretation.
std::thread at a glance
std::thread is the OS-thread API.
thread::spawn(|| { ... })returns aJoinHandle<T>; closure must be'static + Send, return type must beSend.thread::scope(|s| { s.spawn(|| { ... }); ... })(since 1.63) creates scoped threads that can borrow non-'staticdata; all scoped threads must finish before the scope returns. ALWAYS preferthread::scopeoverthread::spawnwhen you need to borrow stack data.JoinHandle::join()waits for the thread and returnsResult<T, Box<dyn Any + Send>>;Errindicates the thread panicked.thread::Builder::new().name("worker").stack_size(8 * 1024 * 1024).spawn(...)configures a thread before spawning.thread::current()returns the current thread handle;thread::sleep(Duration);thread::park()/Thread::unpark().thread_local!macro declares per-thread static storage.
ALWAYS join spawned threads, NEVER let them detach silently unless you genuinely have a fire-and-forget background task.
std::time at a glance
| Type | Purpose |
|---|---|
Duration |
Span of time; constructors Duration::from_secs, from_millis, from_micros, from_nanos. Arithmetic via +, -, *, /. |
Instant |
Monotonic clock; Instant::now(), Instant::elapsed, Instant::duration_since. ALWAYS use for measuring elapsed time. |
SystemTime |
Wall-clock time; SystemTime::now(), SystemTime::UNIX_EPOCH. NEVER use for measuring elapsed time; the wall clock can jump backwards. |
thread::sleep(Duration::from_millis(100)) sleeps the current thread.
For async sleep, use tokio::time::sleep ; NEVER use thread::sleep inside an async runtime.
std::env at a glance
| Function | Returns |
|---|---|
env::args() |
Args iterator over String command-line arguments (panics on non-UTF-8 on Unix; use env::args_os() for OsString) |
env::var("NAME") |
Result<String, VarError>; UTF-8 only |
env::var_os("NAME") |
Option<OsString>; any bytes |
env::vars() |
Iterator over (String, String) pairs (UTF-8 only) |
env::current_dir() |
io::Result<PathBuf> |
env::set_current_dir(path) |
io::Result<()> ; process-wide, NOT thread-local |
env::current_exe() |
io::Result<PathBuf> ; path of the running binary |
env::set_var(k, v) / env::remove_var(k) |
Modifies the process environment (since 1.85: unsafe-marked; calling concurrently from multiple threads is undefined behavior) |
ALWAYS use env::args_os() when arguments may contain non-UTF-8 (filenames on Unix). NEVER mutate environment variables (set_var / remove_var) from inside a library or from a multi-threaded program without external synchronization; libc setenv is not thread-safe.
std::ffi at a glance
Two pairs of borrowed/owned string types, with different invariants.
| Type | Purpose |
|---|---|
CStr |
Borrowed NUL-terminated C string. Unsized, like str. |
CString |
Owned NUL-terminated C string. Constructed via CString::new(...). |
OsStr |
Borrowed platform-native string. Unsized. |
OsString |
Owned platform-native string. |
ALWAYS use CString / CStr for FFI with C APIs (extern "C" functions that take *const c_char). NEVER pass &str directly; &str is not NUL-terminated.
ALWAYS use OsString / OsStr for paths and environment variables that may contain non-UTF-8 bytes (Unix paths, Windows UTF-16 paths).
Conversion: CString::new("hello")? returns Result<CString, NulError>; the error fires if the input contains an interior NUL byte.
See [[rust-impl-ffi-bindgen]] for full FFI mechanics.
The no_std switch
To make a crate compile without std, add #![no_std] to src/lib.rs. Consequences:
- You lose
std::fs,std::io,std::net,std::process,std::thread,std::env,std::sync::Mutex,HashMap,HashSet, the standardprintln!macro that targets stdout. - You keep all of
core:Option,Result,Iterator, traits, atomics, formatting machinery (but no default output target). - ALWAYS add
extern crate alloc;if you wantBox,Vec,String,Rc,Arc,BTreeMap. Thenuse alloc::vec::Vec;, etc. - ALWAYS provide a
#[panic_handler]somewhere in the crate graph (typically in the binary or board crate, not in the library). - For hash maps without
std, use thehashbrowncrate directly withBuildHasherDefault<FxHasher>or a fixed seed.
See [[rust-impl-no-std]] for the full no_std workflow.
Common cross-references
- For ownership and borrowing rules used throughout the stdlib API: see [[rust-syntax-ownership]] and [[rust-syntax-borrowing]].
- For deeper async runtime semantics (when
stdblocking calls are forbidden): see [[rust-core-async-runtime]] and [[rust-impl-async-tokio]]. - For lock-free programming with
std::sync::atomic: see [[rust-impl-concurrency]]. - For channel patterns built on
std::sync::mpsc: see [[rust-impl-channels]]. - For the
no_stdworkflow end to end: see [[rust-impl-no-std]].
Reference files
references/methods.md: exact module item names per area, grouped bystdmodulereferences/examples.md: minimal working example for each major areareferences/anti-patterns.md: five-plus stdlib anti-patterns and their fixes
Approved sources
- Rust Standard Library (root): https://doc.rust-lang.org/std/
- std::collections : https://doc.rust-lang.org/std/collections/index.html
- std::sync : https://doc.rust-lang.org/std/sync/index.html
- std::io : https://doc.rust-lang.org/std/io/index.html
- std::fs : https://doc.rust-lang.org/std/fs/index.html
- std::thread : https://doc.rust-lang.org/std/thread/index.html
- std::time : https://doc.rust-lang.org/std/time/index.html
- std::path : https://doc.rust-lang.org/std/path/index.html
- std::env : https://doc.rust-lang.org/std/env/index.html
- std::ffi : https://doc.rust-lang.org/std/ffi/index.html
- std::process : https://doc.rust-lang.org/std/process/index.html
- Rust 1.70 release (OnceLock) : https://blog.rust-lang.org/2023/06/01/Rust-1.70.0/
- Rust 1.80 release (LazyLock) : https://blog.rust-lang.org/2024/07/25/Rust-1.80.0/
- Rust 1.63 release (scoped threads) : https://blog.rust-lang.org/2022/08/11/Rust-1.63.0/