Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to talk to the operating system

Files, paths and directories, arguments and environment variables, standard streams, child processes, signals, sockets, and calling the OS directly. The standard library door for each.

Name the thing you need from outside the program, then use the standard library's door for it before reaching for a crate or unsafe.

  • Data that should outlive the program → a file (std::fs), named with Path/PathBuf, never by gluing strings together. If a crash mid-save would corrupt it, write a temporary file and rename.
  • Instructions from whoever started the program → command-line arguments (std::env::args, or clap for anything real) and environment variables (std::env::var).
  • Output → results to standard output, messages and progress to standard error, and an exit code a script can test.
  • Another program's help → std::process::Command, with pipes if you feed it input or read its output as it arrives.
  • Being told to stop (Ctrl-C, a container shutting down) → a signal handler that sets a flag, then a graceful shutdown.
  • Another machine → a TCP or UDP socket, or HTTP on top of one.
  • Something the standard library doesn't wrap → a crate like nix or windows-sys first; your own extern declaration and unsafe last.

Files and directories

fs::read_to_string for small files, a BufReader for big ones.

Section

Paths

Path and PathBuf, joined and taken apart the same way on every OS.

Section

Directories

Listing, creating and walking a tree.

Size, times and permission bits, and where Unix and Windows differ.

Missing files, permission errors, and the atomic replace.

tempfile for scratch files; mmap when a huge file should look like a slice.

The process itself

env::args, env::var, and why argument 0 is the program.

stdin, stdout and stderr, locked and buffered.

Formal. A process has three standard streams: standard input, standard output (println!, io::stdout()), and standard error (eprintln!, io::stderr()). Shells redirect and pipe standard output (>, |) while standard error still goes to the terminal, and the process's exit status (0 for success, non-zero for failure) is how scripts and other programs learn whether it worked. io::stdout() is line-buffered; lock it (stdout().lock()) or wrap it in a BufWriter for heavy output.

In plain English. Your program's results go out one door and its complaints go out another, so someone saving the results to a file still sees the complaints on screen. The exit number is the one-word summary: 0 means "it worked".

Example. mytool > out.txt puts println! lines in the file while an eprintln!("warning: ...") still appears in the terminal.

Why this rule exists. Command-line programs are joined together by pipes and scripts; mixing messages into the data, or reporting failure with status 0, breaks every program downstream.

Results on standard output so they can be piped; everything else on standard error.

Catch Ctrl-C, finish the current job, then exit.

Other programs

Command, its arguments passed separately so nothing is ever shell-parsed.

Feed a child's input and read its output while it runs.

Other machines

TcpListener and TcpStream from the standard library.

Section

UDP

Single datagrams with no connection, for when losing one is acceptable.

Underneath the standard library

System calls, file descriptors, and the crates that wrap what std doesn't.

The last door, an extern block and unsafe.

What's left of the standard library when there's no operating system to ask.