| name | rust-core-stdlib-overview |
| description | Use when the user needs a map of the Rust standard library: which std module provides what (collections, sync, io, fs, process, thread, time, path, env, ffi), the prelude, the core / alloc / std split, no_std switch, hash-DoS resilience. Prevents reaching for an external crate when std already has it, missing the no_std implications of using std::collections, or ignoring HashMap's RandomState hash-DoS protection. Covers: std::collections (Vec / HashMap / BTreeMap / VecDeque / HashSet / BTreeSet / BinaryHeap), std::sync (Arc / Mutex / RwLock / atomic / Once / OnceLock 1.70 / LazyLock 1.80 / Barrier / Condvar), std::io (Read / Write / BufReader / stdin / stdout / stderr), std::fs, std::process (Command), std::thread (spawn / scope 1.63+), std::time, std::path, std::env, std::ffi (CString / OsString), prelude, core / alloc / std split, hashbrown. Keywords: stdlib, standard library, prelude, no_std, alloc, core, collections, HashMap, Vec, BTreeMap, Arc, Mutex, RwLock, atomic, LazyLock, OnceLock, Read trait, Write trait, BufReader, File, Path, PathBuf, env vars, Command, std::process, thread spawn, scoped threads, "which crate", "is there std", "do I need a crate", hashbrown, RandomState, hash-DoS.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Rust 1.85+, edition 2024. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
rust-core-stdlib-overview
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, and std"
- User asks "how do I make my crate
no_std" at the map level (mechanics live in rust-impl-no-std)
- User confused about which
std::sync primitive to pick at the catalogue level
- User asks "which
std::collections type 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 |
|
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 implement Hash + 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_front is O(1).
- ALWAYS use
BinaryHeap<T> for priority queues; it is a max-heap, use std::cmp::Reverse for 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. Vec or VecDeque is 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
HashMap iteration order for any logic.
- For deterministic order, use
BTreeMap or wrap with IndexMap (external crate indexmap).
- For maximum speed when keys are trusted (internal benchmark, fixed dataset), use
HashMap with a non-random hasher such as FxHashMap (external crate rustc-hash) or AHashMap (external crate ahash). ALWAYS document the threat model when removing DoS protection.
- The underlying implementation of
HashMap is the hashbrown crate, which is the Swiss-table design used by ABSL. In no_std + alloc you depend on hashbrown directly.
std::sync at a glance
std::sync collects the synchronization primitives.
Arc<T> : atomic reference count, thread-safe shared ownership. ALWAYS use Arc, NEVER Rc, when sharing across threads.
Mutex<T> : exclusive lock. Returns LockResult<MutexGuard<T>>; Err indicates 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 prefer Mutex for write-heavy workloads; RwLock overhead beats Mutex only when reads dominate.
atomic (AtomicUsize, AtomicBool, AtomicPtr, AtomicI32, etc.) : lock-free integer / pointer operations with explicit Ordering. ALWAYS pick Ordering::Relaxed for counters with no cross-thread observation requirements; Ordering::Acquire / Ordering::Release for synchronization; Ordering::SeqCst only when you have proven a need.
Once : run a closure exactly once across threads. Legacy; ALWAYS prefer OnceLock or LazyLock for new code.
OnceLock<T> (since 1.70) : one-time initialization that returns &T. Replaces lazy_static for non-closure cases.
LazyLock<T, F> (since 1.80) : static lazily initialized by a closure on first access. Replaces lazy_static for closure cases.
Barrier : block N threads until all arrive.
Condvar : condition variable, used with Mutex<bool> or Mutex<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 a String; fs::write(path, contents) writes a slice or string.
fs::metadata(path) returns Metadata (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 of io::Result<DirEntry>.
Path is the borrowed unsized type (analogous to &str); PathBuf is the owned growable type (analogous to String).
- ALWAYS accept
&Path (or generically impl AsRef<Path>) in function parameters, NEVER &PathBuf.
- ALWAYS use
Path::join, Path::with_extension, Path::file_name, Path::components for 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 and OsStr for 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()?;
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 collects Output { status, stdout, stderr }.
.status() runs to completion and returns only the exit status.
.spawn() returns Child for 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 a JoinHandle<T>; closure must be 'static + Send, return type must be Send.
thread::scope(|s| { s.spawn(|| { ... }); ... }) (since 1.63) creates scoped threads that can borrow non-'static data; all scoped threads must finish before the scope returns. ALWAYS prefer thread::scope over thread::spawn when you need to borrow stack data.
JoinHandle::join() waits for the thread and returns Result<T, Box<dyn Any + Send>>; Err indicates 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 standard println! 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 want Box, Vec, String, Rc, Arc, BTreeMap. Then use 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 the hashbrown crate directly with BuildHasherDefault<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
std blocking 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_std workflow end to end: see [[rust-impl-no-std]].
Reference files
references/methods.md : exact module item names per area, grouped by std module
references/examples.md : minimal working example for each major area
references/anti-patterns.md : five-plus stdlib anti-patterns and their fixes
Approved sources