This episode breaks down how Rust works behind the scenes: compilation via LLVM, the borrow checker, and the ownership model. You will also get to know cargo as a build system, package manager, and workspace manager, along with the main components such as crate, module, trait, impl, and generic.

Episode 1 explained why Rust exists. Episode 2 breaks down how Rust works. You will see the journey of code from source file to binary: compilation via LLVM, ownership checking by the borrow checker, and the orchestration of all of it by cargo.
Understanding this architecture is not just theoretical knowledge. When the compiler rejects your code, an understanding of the borrow checker and the ownership model will make error messages feel like hints, not obstacles. Cargo will also be the main vehicle for every project in this series.
Rust does not compile directly to machine language. Its pipeline is layered: the parser turns source into an AST, then into HIR and MIR (mid-level intermediate representation), then various checks and optimizations are performed, and finally it is converted into machine code by LLVM.
LLVM is a mature compiler infrastructure, also used by C, C++, and Swift. Rust uses it to produce efficient code on many platforms. That is why the supported targets are very broad: from embedded ARM to x86-64 servers and WebAssembly.
cargo build
cargo build --releasecargo build produces a debug binary that is fast to compile, while cargo build --release enables full optimizations. cargo build --release produces the binary in target/release that is used for production. Optimizations take longer but the performance is far better.
The core rule of Rust: every value has exactly one owner, and the value is freed when the owner goes out of scope. There is no garbage collector running in the background — memory deallocation happens deterministically right at the end of the scope.
To use a value without moving ownership, Rust uses references: one mutable reference or many immutable references, never both at the same time for the same value. The borrow checker is the part of the compiler that verifies these rules.
cat > borrow.rs <<'EOF'
fn hitung_panjang(s: &String) -> usize {
s.len()
}
fn main() {
let pesan = String::from("belajar rust");
let panjang = hitung_panjang(&pesan);
println!("panjang: {}", panjang);
}
EOF
rustc borrow.rs -o borrow
./borrowhitung_panjang(&pesan) borrows pesan immutably, so pesan remains valid after the function finishes. Try changing &String to &mut String and you will see the borrow checker enforce its rules.
Every reference has a lifetime: how long that reference is valid. Most of the time lifetimes can be inferred automatically by the compiler. Explicit annotations like 'a are only needed when several references are interrelated — this detail will be discussed thoroughly in episode 6.
Cargo is not just a compiler wrapper. It handles:
Cargo.toml and Cargo.lock.Cargo.toml is the manifest that declares metadata, dependencies, features, and the edition. Cargo.lock pins the exact version of every dependency so that builds are reproducible.
cargo metadata
cargo treecargo metadata displays complete project information in JSON format, while cargo tree displays the dependency tree. Both are useful for auditing and debugging — you will use cargo tree often in episode 7.
Rust organizes code into four levels:
Cargo.toml and one or more crates.main.rs) or a library (with lib.rs).mod.cargo new --lib kalkulator
find kalkulator -type fcargo new --lib kalkulator creates a library crate with src/lib.rs. A library crate is the foundation for building code that can be tested and reused, while binaries are usually thin and only call into the library.
A trait is a contract of behavior: it defines the methods a type must have. An impl is the implementation of that trait for a specific type. The combination of the two enables generic code that works for any type satisfying the contract.
Generics allow a function to write one definition that works for many types, through monomorphization: the compiler generates type-specific code at compile time. This is an example of a zero-cost abstraction — an abstraction that adds no runtime cost.
cat > trait_generic.rs <<'EOF'
trait Deskripsi {
fn deskripsi(&self) -> String;
}
struct Server {
nama: String,
}
impl Deskripsi for Server {
fn deskripsi(&self) -> String {
format!("server {}", self.nama)
}
}
fn cetak<T: Deskripsi>(item: &T) {
println!("{}", item.deskripsi());
}
fn main() {
let s = Server { nama: String::from("api-utama") };
cetak(&s);
}
EOF
rustc trait_generic.rs -o trait_generic
./trait_genericfn cetak<T: Deskripsi>(item: &T) is a generic function that accepts any type implementing Deskripsi. Episode 5 will break down traits and generics in depth.
Key takeaways:
In the next episode 3 we will discuss writing your first Rust program — the structure of binary and library crates, basic data types, variable immutability and shadowing, functions and expressions, as well as control flow and pattern matching. You will start writing real Rust code.