In this lesson, you will learn how the linker performs the final act of symbol resolution to transform your compiled object files into an executable binary.
The Final Assembly
Up until the linking phase, the Rust compiler (rustc) works on individual translation units. It generates machine code for your crate and its dependencies, emitting "object files" (.o or .obj). However, these files are incomplete; they contain "undefined references" to functions or variables that reside in other compiled modules or external libraries.
The linker’s primary responsibility is symbol resolution. It acts as a master accountant, scanning through all your object files and library archives to map these abstract references to concrete memory addresses.
How Resolution Works
Consider a scenario where you call an external function or a function defined in a different module:
// In module A
pub fn calculate() { /* ... */ }
// In module B
extern "C" {
fn calculate();
}
fn main() {
unsafe { calculate(); }
}
When rustc compiles module B, it marks calculate as an extern symbol—a placeholder. It doesn't know where calculate lives in the final memory layout. During the linking phase, the linker examines the symbol table of all provided objects. It sees that module B needs a symbol named calculate and that module A provides a definition for it.
The linker performs two main steps:
- Symbol Binding: It matches the reference in module B to the address of
calculatedefined in module A. - Relocation: It patches the machine code in module B, replacing the placeholder address with the actual relative or absolute memory offset where
calculatewill reside in the final binary.
If the linker cannot find a matching definition for a symbol, it throws the classic "undefined reference" error. If it finds multiple definitions for the same symbol, it triggers a "duplicate symbol" error. By the time the linker finishes, it has stitched together these fragmented object files into a single, coherent memory map where every instruction knows exactly which address it is jumping to.
Key Takeaway: The linker resolves the final location of symbols by matching undefined references in object files to their concrete addresses, patching the machine code so the resulting binary can execute as a cohesive program.