9.3 Forged
The artifact has passed the gate. Tests pass, the port proves itself, the ledger is kept, the door closes cleanly. All of this - in the workshop, with a debug build: no optimizations, function names and line numbers that the debugger needs and the user does not. The workshop is not the world. What goes out into it is assembled by different rules.
What You Need
cargo build is a debug build. The compiler is in a hurry: no optimizations, everything is
kept. The tq binary in target/debug/ weighs 21 megabytes. Inside - the names of every
function, source line numbers, data for the debugger. The user needs none of it.
cargo build --release activates a different profile. The compiler stops hurrying: it
applies optimizations, removes dead code. The profile can be configured in the root
Cargo.toml - that is where you define what “release” means for this project. Two settings
have the largest effect.
lto = true - link-time optimization. Normally each crate compiles separately; the linker
joins the object files afterward. With lto = true, the compiler sees the entire program
graph at link time: it removes functions nobody calls, inlines small functions, eliminates
branches that never execute. The binary is smaller and runs faster.
strip = true - discards debug symbols from the final binary. Function names, source paths,
line numbers - none of it is needed by the user.
The Build
Add a section to the root Cargo.toml after [workspace]:
[profile.release]
lto = true
strip = true
In the Makefile, add a target in the BUILD section, before clean:
## release: build optimized stripped binaries
.PHONY: release
release:
@cargo build --release -p tq-cli -p tq-api
@ls -lh target/release/tq target/release/tq-api
The Result
$ make release
Finished `release` profile [optimized] target(s) in 53.72s
-rwxrwxr-x 1 user user 1.1M target/release/tq
-rwxrwxr-x 1 user user 1.7M target/release/tq-api
target/debug/tq weighed 21 megabytes. target/release/tq - 1.1. Twenty times smaller.
The debug binary is a draft with margin notes. What leaves the workshop is the finished piece.
The finished artifact runs without Rust:
$ ./target/release/tq list
$ ./target/release/tq-api
tasks.json persists between runs - data from earlier chapters is still there.
make ci verifies correctness. make release builds what gets shipped.
The complete
tqcode for this chapter is in9-shippable/03-forged/.
Lore: What the Debug Build Contains
In a debug build the binary contains a .debug_info section - data in DWARF format. It
holds variable and function names, source file paths, line numbers, types. The debugger
(gdb, lldb) reads this section to show “you stopped at line 42 in store.rs” instead
of “you stopped at address 0x4a3f12”. strip = true removes this section; the program’s
behavior does not change.
You can remove only part of it: strip = "debuginfo" removes the debug data but keeps
symbol names - useful if you need to profile a release build.
[profile.release] accepts many settings: opt-level, codegen-units, panic,
overflow-checks, and others. The right combination depends on the task - an embedded
system, a server application, and a CLI each require different trade-offs. The full list
with explanations is in the
Cargo documentation.