Dheerendra Varma
Back to Blog
1/15/20263 min read

Understanding Rust: Ownership, Borrowing, and Lifetimes

A deep dive into the core concepts that make Rust memory-safe without a garbage collector.

Understanding Rust: Ownership and Borrowing

Rust's most unique feature is Ownership, a system that manages memory through a set of rules the compiler checks at compile time. This allows Rust to provide memory safety guarantees without needing a garbage collector.

The Three Rules of Ownership

  1. Each value in Rust has a variable that’s called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

Move Semantics

In languages like Python or JavaScript, assigning one variable to another typically creates a shallow copy or a reference. In Rust, for types that don't implement the Copy trait (like String), the value is moved.

let s1 = String::from("hello");
let s2 = s1; // s1 is moved to s2

// println!("{}", s1); // This would cause a compile-time error!
println!("{}", s2); // This works

Borrowing: References and the Rules

Borrowing allows you to traverse data without taking ownership. We do this using references (&).

The Golden Rule of References

You can have:

  • Unlimited immutable references (&T).
  • Exactly one mutable reference (&mut T).
  • But not both at the same time.
let mut s = String::from("hello");

let r1 = &s; // Fine
let r2 = &s; // Fine
// let r3 = &mut s; // ERROR: Cannot borrow as mutable because it is also borrowed as immutable

println!("{} and {}", r1, r2);
// r1 and r2 go out of scope here

let r3 = &mut s; // Now this is fine

Edge Cases: NLL (Non-Lexical Lifetimes)

In older versions of Rust, a reference's scope lasted until the end of the block. Modern Rust is smarter. If the compiler can prove a reference is no longer used, its scope ends early.

let mut s = String::from("hello");

let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point

let r3 = &mut s; // This is valid in modern Rust!
r3.push_str(", world");

Common Pitfalls: Dangling References

Rust prevents you from creating a reference to a value that has already been dropped.

/* This won't compile
fn dangle() -> &String {
    let s = String::from("hello");
    &s // s is dropped here, so we'd return a reference to invalid memory
}
*/

fn no_dangle() -> String {
    let s = String::from("hello");
    s // We return the String itself, moving ownership to the caller
}

Conclusion

Ownership and Borrowing might feel restrictive at first, but they are the secret sauce that makes Rust both fast and safe. By moving memory management to compile-time, Rust eliminates entire classes of bugs like null pointer dereferences and data races.