Ownership β Doing What Ruby's GC Did for You
The core Rust concept Rubyists struggle with most
Have you ever thought about memory management in Ruby? Probably not. GC handles it. Create objects, assign to variables, pass to methods, GC cleans up when you're done.
Rust has no GC. Instead, three ownership rules manage memory.
Rule 1: Every value has one owner
let s1 = String::from("hello");
let s2 = s1; // ownership moves from s1 to s2
// println!("{}", s1); // compile error! s1 no longer valid
In Ruby, s2 = s1 makes both point to the same object. Both usable. In Rust, s1 disappears. Only s2 remains.
This is the most shocking part for Rubyists. "I just assigned it to a variable and the original is gone?"
Why this design
Ruby's GC tracks "is anyone still using this object?" at runtime. That costs CPU time and memory. Rust does this tracking at compile time, making runtime cost zero.
Rule 2: Borrowing β passing by reference
You can lend without transferring ownership.
fn print_len(s: &String) { // & means borrow
println!("{}", s.len());
}
let s = String::from("hello");
print_len(&s); // lend with &
println!("{}", s); // s still usable!
& means "I'll just read, you keep ownership." In Ruby, this distinction wasn't needed β all object passing is by reference.
Rule 3: Only one mutable borrow
let mut s = String::from("hello");
let r1 = &s; // immutable ref β OK
let r2 = &s; // another immutable ref β OK
// let r3 = &mut s; // mutable ref β error! can't while immutable refs exist
"Multiple readers OK, one writer OK, but no writing while anyone's reading." This rule prevents data races at compile time.
Clone β Ruby's .dup
When you don't want to transfer ownership and borrowing doesn't fit β copy.
let s1 = String::from("hello");
let s2 = s1.clone(); // deep copy
println!("s1: {}, s2: {}", s1, s2); // both OK
Same as Ruby's .dup/.clone. But has performance cost β use only when needed.
Copy β stack values are the exception
Small values like integers, booleans, floats are auto-copied.
let x = 42;
let y = x; // copied (not moved)
println!("{}", x); // OK! integers implement Copy trait
Stack values are cheap to copy, so Rust copies automatically. String is heap data β no auto-copy.
Practical pattern
Think in this order:
- Can I borrow with &? β Usually sufficient
- Must I transfer ownership? β Only when function needs to own
- Do I need clone? β When neither above works
Key Points
Every value has one owner β let s2 = s1; moves ownership
Borrow with & keeps ownership β usually sufficient
Multiple immutable refs OK, one mutable only β prevents data races
clone() for deep copy β like Ruby .dup
Pros
- ✓ Memory safety without GC β zero runtime cost
- ✓ Data races caught at compile time
Cons
- ✗ Steep initial learning curve since Ruby has no equivalent concept
- ✗ Fighting the compiler takes significant time initially