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 |
|---|---|
|
new binary crate. |
|
new library crate. |
|
debug build ( |
|
optimised build ( |
|
build and run. |
|
run unit + integration + doc tests. |
|
type-check without codegen; fastest feedback. |
|
generate and view docs. |
|
format every file ( |
|
lint catalogue. |
|
install a binary crate to |
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 |
|---|---|
|
default Linux x86-64. |
|
static binary (musl libc); no glibc dependency. |
|
Linux ARM64. |
|
macOS Apple Silicon. |
|
macOS Intel. |
|
Windows MSVC toolchain. |
|
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,
Copyvalues, fixed-size arrays.Heap:
Box<T>,Vec<T>,String,Rc,Arc. Allocator-backed.Static:
static/constvalues;'staticreferences.
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#
Tooling for
rustup/clippy/miriand the wider toolchain.Functions for
const fnandasync fncompilation.Variables for the drop semantics and static storage.
Rustonomicon for the
unsaferules.