SuperAPI AI Directory

rust-core-stdlib-overview

Coding & development

View repository

Updated 10-15 22:18

About this skill

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
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 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()?; // 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 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