This episode breaks down the heart of Rust's memory safety: the ownership and borrowing rules for immutable and mutable references, basic lifetime annotations and reference validity, plus the practice of transferring ownership with move, clone, and copy. You will understand why many Rust programs compile without memory bugs.

This episode covers the material that most distinguishes Rust from other languages. The concepts of ownership, borrowing, and lifetimes are the reason Rust can promise memory safety without a garbage collector. Many beginners give up here, but with the right perspective, the rules actually make sense and are consistent.
Episode 6 breaks down all three concepts one by one, then closes with the practice of transferring ownership: move, clone, and copy. You will also learn to read borrow checker errors, which have long been a source of frustration.
Ownership in Rust is summarized in three rules:
cat > src/main.rs <<'EOF'
fn main() {
let teks = String::from("hello");
println!("di scope utama: {}", teks);
}
EOF
cargo runteks is owned by main. At the end of the scope, the String is freed automatically — no free or delete needed, and no waiting for a garbage collector. This rule is what makes memory allocation deterministic.
Fixed-size types like i32 live on the stack; data with dynamic size like the contents of a String is allocated on the heap with a pointer on the stack. When the owner is dropped, the heap portion is freed along with it. Ownership determines when that happens.
Borrowing means using a value without taking ownership, via references. Immutable references (&T) allow many readers at once; mutable references (&mut T) allow a single writer. Both cannot be active at the same time for the same value. Use cargo check to verify these rules without producing a binary.
cat > src/main.rs <<'EOF'
fn hitung(s: &String) -> usize {
s.len()
}
fn main() {
let teks = String::from("borrowing");
let a = &teks;
let b = &teks;
println!("{} dan {}", hitung(a), hitung(b));
}
EOF
cargo runTwo immutable references a and b are allowed to coexist. The borrow checker ensures there is no data race: data being borrowed immutably cannot be mutated from elsewhere.
To change a value through a reference, use &mut:
cat > src/main.rs <<'EOF'
fn tambahkan(s: &mut String) {
s.push_str("!");
}
fn main() {
let mut teks = String::from("rust");
tambahkan(&mut teks);
println!("{}", teks);
}
EOF
cargo run&mut teks borrows mutably. Only one mutable borrow may be active per value. If you try to borrow mutably twice or combine it with an immutable borrow, the compiler will reject it — and its message usually explains the conflict clearly.
A lifetime is the period during which a reference is valid. Every reference has a lifetime; most of the time the compiler infers it. Explicit annotations are needed when several references of a function are related and the compiler cannot determine the relationship.
cat > src/main.rs <<'EOF'
fn pilih<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("pendek");
let b = String::from("lebih panjang");
let hasil = pilih(&a, &b);
println!("{}", hasil);
}
EOF
cargo run<'a> declares the lifetime 'a, and &'a str states that both parameters and the return type share the same lifetime. This means: the returned reference is only valid as long as both inputs are still alive. This function cannot return a dangling reference.
Rust has lifetime elision rules: if a function has a single reference parameter, its return type is considered to use that parameter's lifetime. That is why most functions do not need to write 'a explicitly. Annotations only appear when there are several reference inputs and the relationship must be asserted.
When a value is passed to a function or assigned to a new variable, its ownership moves. The original value can no longer be used:
cat > src/main.rs <<'EOF'
fn konsumsi(s: String) {
println!("memakai: {}", s);
}
fn main() {
let teks = String::from("data");
konsumsi(teks);
// println!("{}", teks); // error: value moved
}
EOF
cargo runAfter konsumsi(teks), the variable teks is no longer valid because its ownership has moved. The compiler prevents use of a moved value — that famous "value moved" error.
To keep using the original value, copy it: Clone makes an explicit deep copy, while Copy copies simple, cheap values when assigned. Types like integers and bool implement Copy; String and Vec are only Clone.
cat > src/main.rs <<'EOF'
#[derive(Clone, Copy)]
struct Titik {
x: i32,
y: i32,
}
fn main() {
let a = Titik { x: 1, y: 2 };
let b = a; // Copy: a tetap valid
println!("{} {}", a.x, b.y);
let teks = String::from("asli");
let salinan = teks.clone(); // Clone eksplisit
println!("{} {}", teks, salinan);
}
EOF
cargo runlet b = a copies Titik because it is Copy. For String, you must call teks.clone() explicitly — Rust's insistence on clarity is exactly what prevents costly accidental copies.
Practical guideline: use borrowing (&) when a function only needs to read; &mut when it needs to change in place; move ownership when the function will store or dispose of the value; and use clone only when a copy is truly necessary. This priority order makes code efficient and easy to understand at the same time.
Key takeaways:
'a states reference validity; elision removes most annotations.Copy for cheap types; Clone for explicit copies.In the next episode 7 we will discuss packages, crates, and dependency management — structuring Cargo.toml, features, and dependency versions, multi-crate workspaces for large applications, as well as dependency security with cargo audit. You will learn to manage code beyond a single file.