Runtime#

The Rust “runtime” is thin: a small panic / unwind handler, a GC-less allocator (system malloc by default), the start-up code that calls main. Most of what looks like runtime in other languages (memory management, lifetime tracking) happens at compile time. The compiler (rustc) plus the project manager (cargo) plus LLVM produce a native binary that talks directly to libc.

/tooling`.

rustc and cargo#

rustc is the compiler; cargo is the build / dependency front-end use daily.

Command

Effect

cargo new <name>

new binary crate.

cargo new --lib <name>

new library crate.

cargo build

debug build (target/debug/).

cargo build --release

optimised build (target/release/).

cargo run

build and run.

cargo test

run unit + integration + doc tests.

cargo check

type-check without codegen; fastest feedback.

cargo doc --open

generate and view docs.

cargo fmt

format every file (rustfmt).

cargo clippy

lint catalogue.

cargo install <name>

install a binary crate to ~/.cargo/bin.

Cargo.toml#

The project manifest.

[package]
name    = "scan"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio   = { version = "1", features = ["full"] }
clap    = { version = "4", features = ["derive"] }
reqwest = "0.12"
anyhow  = "1"

[dev-dependencies]
proptest = "1"

[profile.release]
opt-level = 3
lto       = "fat"
codegen-units = 1
panic     = "abort"
strip     = true

Cargo.lock records the resolved exact versions. Commit it for binaries; for libraries the convention is to leave it out (the consumer locks).

Editions#

Editions group syntax and lint changes. Editions in 2026: 2015, 2018, 2021, 2024. Each project picks one; the compiler accepts a mix across crates and bridges the differences.

[package]
edition = "2021"

Pick 2021 (or 2024); cargo fix --edition migrates an older project.

Crates and modules#

A crate is the compilation unit (a binary or a library). A module is a namespace inside it.

src/
  main.rs            # binary crate root
  scan/
    mod.rs           # `mod scan;` from main.rs
    tcp.rs           # `pub mod tcp;` from scan/mod.rs
// src/main.rs
mod scan;
use scan::tcp::Probe;

fn main() { Probe::new().run(); }

Visibility: pub exports; pub(crate) is crate-internal; pub(super) is parent-module-internal.

Workspaces#

A workspace groups related crates under one Cargo.toml. Useful in monorepos; shared target/ directory and a single Cargo.lock.

[workspace]
members = ["scan", "scan-cli", "shared"]
resolver = "2"

Targets and cross-compilation#

rustup target add adds a compilation target; cargo build --target cross-compiles.

$ rustup target add x86_64-unknown-linux-musl
$ cargo build --release --target x86_64-unknown-linux-musl

Common operator targets.

Triple

Notes

x86_64-unknown-linux-gnu

default Linux x86-64.

x86_64-unknown-linux-musl

static binary (musl libc); no glibc dependency.

aarch64-unknown-linux-gnu

Linux ARM64.

aarch64-apple-darwin

macOS Apple Silicon.

x86_64-apple-darwin

macOS Intel.

x86_64-pc-windows-msvc

Windows MSVC toolchain.

wasm32-unknown-unknown

bare WebAssembly.

For Linux cross-builds use cross (cargo install cross), which runs the target compiler inside a Docker container so do not need the toolchain locally.

no_std#

#![no_std] opts out of the standard library; the crate sees only core. Used for embedded, kernel modules, eBPF probes, bootloaders.

#![no_std]
#![no_main]

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

core has everything that does not need an allocator; alloc adds Vec, String, Box (still no I/O).

The compile pipeline#

Stage

Output

Parse + macro expansion

AST.

Name resolution + type check + borrow check

HIR / MIR (Mid-level Intermediate Representation).

Code generation

LLVM IR.

LLVM

object file.

Linker

executable or shared library.

The operator rarely cares about the steps; cargo build hides them. cargo expand (a plugin) shows macro expansion; cargo asm shows the generated assembly.

Memory model#

  • Stack: function locals, Copy values, fixed-size arrays.

  • Heap: Box<T>, Vec<T>, String, Rc, Arc. Allocator-backed.

  • Static: static / const values; 'static references.

The default allocator is the system malloc (jemalloc historically; you can override with #[global_allocator]).

Drop#

Every value runs its Drop impl when its owner goes out of scope. Do not implement Drop by hand often; the stdlib types (Box, Vec, File, Mutex) do it already.

struct LogScope { name: &'static str }
impl Drop for LogScope {
    fn drop(&mut self) { println!("leaving {}", self.name); }
}

{
    let _g = LogScope { name: "main" };
    work();
}                            // prints "leaving main" on the way out

unsafe and FFI#

unsafe opts into raw pointers, mutable statics, and FFI. extern "C" declares C-ABI functions; #[no_mangle] keeps the symbol name.

extern "C" {
    fn strlen(s: *const u8) -> usize;
}

let s = b"hello\0";
let n = unsafe { strlen(s.as_ptr()) };

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }

For binding C libraries cleanly, bindgen generates Rust FFI declarations from a C header.

panic mode#

Default unwind: panic! runs Drop impls up the stack, the calling thread terminates, catch_unwind can recover. abort skips unwinding; smaller binaries, no catch_unwind; used for embedded / no_std.

[profile.release]
panic = "abort"

References#