Polyglot
Languages Rust stdlib
Rust § stdlib

Standard library

The Rust standard library (std) is comparatively small but covers the substance of routine programming: collections, strings, I/O, threads, synchronisation, time, networking, file system, error handling, and the conventional traits. The library is layered: core admits no-allocation, no-OS dependencies (suitable for embedded no_std environments); alloc admits allocation (Vec, String, Box); std adds OS-dependent features (file I/O, threads, networking). The conventional Rust application uses the full std; embedded and constrained environments often use only core or core + alloc.

This tour points out the principal modules and their conventional uses.

std::collections

The standard collection types:

use std::collections::{
    Vec,                                         // (re-exported from prelude)
    HashMap, BTreeMap,
    HashSet, BTreeSet,
    VecDeque, LinkedList,
    BinaryHeap,
};

Treated in Data structures.

The conventional default is Vec<T> for sequences, HashMap<K, V> for key-value maps, HashSet<T> for sets. Tree-based versions (BTreeMap, BTreeSet) admit ordered iteration and range queries.

std::string and std::str

The string types: String (owned, growable, UTF-8) and str (the unsized slice type, conventionally seen as &str):

let s: String = String::from("hello");
let slice: &str = &s;

s.len();
s.is_empty();
s.contains("ell");
s.starts_with("he");
s.ends_with("lo");
s.replace("l", "r");
s.to_uppercase();
s.to_lowercase();
s.trim();
s.split(" ").collect::<Vec<&str>>();
s.chars().count();                              // character count (not bytes)

Treated in Strings.

std::io

The I/O surface:

use std::io::{self, Read, Write, BufRead, BufWriter, BufReader};

// stdin/stdout/stderr:
let mut stdin = io::stdin();
let mut stdout = io::stdout();
let mut stderr = io::stderr();

// Read a line:
let mut line = String::new();
stdin.read_line(&mut line)?;

// Buffered I/O (much faster):
let stdin = io::stdin();
let reader = stdin.lock();
for line in reader.lines() {
    println!("{}", line?);
}

Treated in I/O and serialisation.

std::fs

File-system operations:

use std::fs;

let contents: String = fs::read_to_string("file.txt")?;
let bytes: Vec<u8> = fs::read("file.bin")?;
fs::write("output.txt", "hello world")?;

fs::create_dir("dir")?;
fs::create_dir_all("a/b/c")?;
fs::remove_file("file.txt")?;
fs::remove_dir("dir")?;
fs::remove_dir_all("a")?;
fs::copy("from.txt", "to.txt")?;
fs::rename("old.txt", "new.txt")?;

let metadata = fs::metadata("file.txt")?;
println!("size: {}", metadata.len());
println!("modified: {:?}", metadata.modified()?);

for entry in fs::read_dir(".")? {
    let entry = entry?;
    println!("{}", entry.path().display());
}

Treated in I/O and serialisation.

std::path

Path manipulation:

use std::path::{Path, PathBuf};

let p = Path::new("/etc/hosts");
println!("{}", p.display());                     // "/etc/hosts"
println!("{:?}", p.file_name());                 // Some("hosts")
println!("{:?}", p.parent());                    // Some("/etc")
println!("{:?}", p.extension());                 // None
println!("{}", p.is_absolute());                 // true

let mut buf = PathBuf::from("/etc");
buf.push("hosts");                               // "/etc/hosts"

let combined = Path::new("/usr").join("local").join("bin");

The Path is the borrowed view; PathBuf is the owned, growable path. The relationship mirrors &str/String.

std::env

Environment and process arguments:

use std::env;

let args: Vec<String> = env::args().collect();
let exe = env::current_exe()?;
let cwd = env::current_dir()?;

let home = env::var("HOME")?;
let path = env::var("PATH").unwrap_or_default();

env::set_var("MY_VAR", "value");

for (key, value) in env::vars() {
    println!("{}={}", key, value);
}

std::process

Process management:

use std::process::{Command, exit};

let output = Command::new("ls")
    .args(&["-la", "/etc"])
    .output()?;

println!("{}", String::from_utf8_lossy(&output.stdout));
println!("{}", String::from_utf8_lossy(&output.stderr));
println!("status: {}", output.status);

// Exit:
exit(1);

// Inherit stdout/stderr:
let status = Command::new("git")
    .arg("status")
    .status()?;

std::time

Time and duration:

use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

let d = Duration::from_secs(5);
let d = Duration::from_millis(500);
let d = Duration::from_micros(100);
let d = Duration::from_nanos(1000);

// Monotonic time (for measurements):
let start = Instant::now();
do_work();
let elapsed = start.elapsed();
println!("took {:?}", elapsed);

// Wall-clock time:
let now = SystemTime::now();
let since_epoch = now.duration_since(UNIX_EPOCH)?;
println!("seconds since epoch: {}", since_epoch.as_secs());

// Sleep:
std::thread::sleep(Duration::from_secs(1));

For elaborate date and time handling (parsing, formatting, time zones), the third-party chrono and time crates are conventional.

std::thread

OS threads:

use std::thread;
use std::time::Duration;

let handle = thread::spawn(|| {
    thread::sleep(Duration::from_secs(1));
    42
});

let result = handle.join().unwrap();

let current = thread::current();
println!("thread name: {:?}", current.name());

let builder = thread::Builder::new()
    .name("worker".to_string())
    .stack_size(2 * 1024 * 1024);

let handle = builder.spawn(|| {
    // ...
})?;

Treated in Concurrency.

std::sync

Synchronisation primitives:

use std::sync::{Arc, Mutex, RwLock, Barrier, OnceLock};
use std::sync::atomic::{AtomicI32, AtomicBool, Ordering};
use std::sync::mpsc;

Treated in Concurrency and Smart pointers.

std::net

Networking:

use std::net::{TcpStream, TcpListener, UdpSocket, SocketAddr, IpAddr, Ipv4Addr};
use std::io::{Read, Write};

// TCP client:
let mut stream = TcpStream::connect("example.com:80")?;
stream.write_all(b"GET / HTTP/1.0\r\n\r\n")?;
let mut response = String::new();
stream.read_to_string(&mut response)?;

// TCP server:
let listener = TcpListener::bind("127.0.0.1:8080")?;
for stream in listener.incoming() {
    let mut stream = stream?;
    handle_connection(&mut stream);
}

// UDP:
let socket = UdpSocket::bind("0.0.0.0:0")?;
socket.send_to(b"hello", "127.0.0.1:8080")?;

For elaborate networking (HTTP clients, TLS, protocol handling), third-party crates are conventional: reqwest for HTTP, tokio for async networking, tungstenite for WebSocket.

std::error

The Error trait:

use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct MyError(String);

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl Error for MyError {}

Treated in Error handling.

std::fmt

Formatting:

use std::fmt::{self, Display, Debug};

struct Point { x: i32, y: i32 }

impl Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Point")
            .field("x", &self.x)
            .field("y", &self.y)
            .finish()
    }
}

let p = Point { x: 1, y: 2 };
println!("{}", p);                               // (1, 2)
println!("{:?}", p);                             // Point { x: 1, y: 2 }

The Debug is conventionally derived (#[derive(Debug)]); Display is implemented for user-facing output.

std::convert

Type conversion traits:

use std::convert::{From, Into, TryFrom, TryInto, AsRef, AsMut};

// From / Into are paired:
let n: i64 = 42i32.into();                       // i32 → i64
let s: String = String::from("hello");

// TryFrom / TryInto for fallible conversions:
let n: i32 = 42i64.try_into()?;
let n = i32::try_from(42i64)?;

// AsRef / AsMut for cheap reference conversions:
fn process<T: AsRef<str>>(s: T) {
    let s: &str = s.as_ref();
    // ...
}

process("hello");                                // works
process(String::from("hello"));                  // works
process(&String::from("hello"));                 // works

The conventional discipline is to take &str, &[T], or generic T: AsRef<...> parameters for substantial flexibility.

std::cmp

Comparison:

use std::cmp::{Ordering, min, max, Ord, PartialOrd, Eq, PartialEq, Reverse};

let a = 3;
let b = 5;
match a.cmp(&b) {
    Ordering::Less => println!("a < b"),
    Ordering::Equal => println!("a == b"),
    Ordering::Greater => println!("a > b"),
}

let smaller = min(3, 5);
let larger = max(3, 5);

let mut v = vec![3, 1, 4, 1, 5];
v.sort();                                        // ascending
v.sort_by(|a, b| b.cmp(a));                      // descending
v.sort_by_key(|x| Reverse(*x));                  // descending via Reverse

std::iter

Iterator adapters and combinators:

use std::iter::{repeat, once, empty, from_fn};

let r = repeat(5).take(10);                     // 5, 5, 5, ...
let o = once(42);
let e: std::iter::Empty<i32> = empty();

let counter = (0..).take(10);                    // infinite 0.. truncated to 10

let mut counter = 0;
let f = from_fn(|| {
    counter += 1;
    if counter <= 5 { Some(counter) } else { None }
});

Treated in Iterators.

std::option and std::result

The Option and Result types and methods:

let opt: Option<i32> = Some(5);
let r: Result<i32, &str> = Ok(5);

opt.unwrap_or(0);
opt.map(|n| n * 2);
opt.and_then(|n| if n > 0 { Some(n) } else { None });
opt.ok_or("missing");
opt.is_some();

r.unwrap_or(0);
r.map(|n| n * 2);
r.map_err(|e| e.to_string());
r.ok();
r.is_ok();

Treated in Error handling.

std::mem

Memory operations:

use std::mem;

mem::size_of::<i32>();                           // 4
mem::size_of_val(&5);                            // 4
mem::align_of::<i32>();                          // 4

let mut a = 1;
let mut b = 2;
mem::swap(&mut a, &mut b);                       // a = 2, b = 1

let v = mem::take(&mut value);                   // replace with default
let v = mem::replace(&mut value, new_value);     // replace with specified

mem::drop(value);                                // drop early

The mem::swap and mem::take are conventional for substituting values without move violations.

std::ops

Operator traits:

use std::ops::{Add, Sub, Mul, Neg, Deref, DerefMut, Index, IndexMut};

#[derive(Debug, Clone, Copy)]
struct Vec2 { x: f64, y: f64 }

impl Add for Vec2 {
    type Output = Vec2;
    fn add(self, rhs: Self) -> Self::Output {
        Vec2 { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

let a = Vec2 { x: 1.0, y: 2.0 };
let b = Vec2 { x: 3.0, y: 4.0 };
let c = a + b;                                   // Vec2 { x: 4.0, y: 6.0 }

Treated in Operators and Traits.

std::marker

Marker traits:

use std::marker::{Send, Sync, Copy, Sized, PhantomData};

The PhantomData<T> admits using a generic parameter without storing a value of T:

struct Tagged<T> {
    value: i32,
    _phantom: std::marker::PhantomData<T>,
}

The mechanism is conventional for type-level state machines and FFI wrappers.

std::any

Type introspection (rarely used):

use std::any::{Any, TypeId};

fn type_id<T: 'static>(_: &T) -> TypeId {
    TypeId::of::<T>()
}

fn print_type<T: Any>(value: T) {
    let any: Box<dyn Any> = Box::new(value);
    if let Some(s) = any.downcast_ref::<String>() {
        println!("string: {}", s);
    } else if let Some(i) = any.downcast_ref::<i32>() {
        println!("integer: {}", i);
    }
}

The Any admits dynamic typing in narrow cases; the conventional Rust style avoids it for static-typed alternatives.

std::ffi

C interop:

use std::ffi::{CString, CStr, OsString, OsStr};

let s = CString::new("hello").unwrap();
let bytes: &[u8] = s.as_bytes_with_nul();

let path = std::env::var_os("PATH");             // OsString — platform-native string

The CString/CStr admit interoperability with C; OsString/OsStr admit handling platform-specific strings (which may not be valid UTF-8, e.g., on Windows).

core and alloc

The standard library’s layers:

  • core — fundamental types and traits, no allocation, no OS.
    • Option, Result, Iterator, primitives, slice operations.
  • alloc — heap-allocated types.
    • Box, Vec, String, Rc, Arc, collections.
  • std — OS-dependent features.
    • File I/O, threads, networking, time.

Embedded code conventionally uses #![no_std] and depends on core (and optionally alloc); std is unavailable on platforms without an OS.

A note on third-party crates

The Rust ecosystem treats many features as third-party rather than standard:

  • Date/timechrono, time.
  • Random numbersrand.
  • Regular expressionsregex.
  • Serialisationserde, serde_json, bincode.
  • Logginglog (facade), env_logger/tracing.
  • HTTP clientreqwest.
  • Async runtimetokio, async-std.
  • Error handlingthiserror, anyhow.
  • CLI parsingclap.
  • Hashingsha2, blake3.
  • UUIDuuid.
  • Web frameworksaxum, rocket, actix-web.
  • Database accesssqlx, diesel, sea-orm.

The conventional Rust application brings in several of these; the standard library focuses on substance and stability rather than feature completeness.

A note on the conventional discipline

The standard-library advice:

  • Use Vec<T>, String, HashMap<K, V> as the conventional defaults.
  • Use the std::fs and std::path for file-system operations.
  • Use std::time::Duration for time spans; Instant for measurements.
  • Use std::thread and std::sync for OS-thread concurrency.
  • Reach for crates.io for elaborate functionality (date/time, HTTP, regex, etc.).
  • Use std::process::Command for spawning subprocesses.
  • Use std::env for environment variables and process arguments.
  • Implement Display on types meant for user output; Debug (typically derived) for development.

The combination — substantial standard library covering routine programming, layered design admitting embedded use, conventions favouring third-party crates for elaborate functionality — is the substance of Rust’s standard library philosophy. The mechanism admits substantial reuse and a stable, well-tested foundation.