Rust: Ownership - 1


NERDSSS. I was re-reading Rust ownership so I decided to write a blog poast about it. I’ll try to keep it simple and smooth as usual and for this one I’ve tried to take a game-like approach so it’d be like a lore-code.

This is Part 1 about Ownership in-general and we’ll focus on how Rust handles who “owns” data, especially stuff on the heap like dynamic strings.

Well here I decided to go with a Treasure Hunt… why? Because ownership is like claiming artifacts. Only one hunter can hold a treasure at a time, and if you drop it (out of scope), it’s gone forever. We’ll go step by step, hitting roadblocks that teach the concepts (hopefully).

First, make sure you have Rust toolchain up and running (rustup is your friend, btw.) and let’s code!!

Setting stuff up

Firstly we have to get started by making a Rust project. This’ll be a simple CLI game called “Rust Island Treasures”. The player starts with an empty backpack (inventory). As they explore, they find treasures represented as Strings because their descriptions can grow or change (like adding notes about curses or values). The goal here is to collect treasures without losing them to the island’s pitfalls (aka Rust’s compiler enforcing ownership rules).

so… to setup a project run:

$ cargo new rust-island-treasures

$ cd rust-island-treasures

now open your beloved text-editor (pls don’t fight over nvim, emacs, c*rsor etc.) just pick one.

Level 1: Discovering your First Treasure (Variables and Scope)

In our game, treasures appear when you enter a cave. Let’s create a treasure as a String why? Just because. nah, I’m just kidding… Because string literals are baked into the binary, are immutable and fixed. Treasures might need to expand (e.g. “Golden Idol” becomes “Golden Idol - Cursed!”).

so… enough yapping time to get rusty

fn main() {
    explore_cave();
}

fn explore_cave() {
    let treasure = String::from("Golden Idol"); // claim ownership here
    println!("You found {}", treasure);
} // treasure drops out of scope here. POOF!! it's freed!!

run this with cargo run

rust-island-treasures [ master][?][📦 v0.1.0][🦀 v1.88.0]
❯ cargo run
   Compiling rust-island-treasures v0.1.0 (/home/syk/dev/rndm/rust-island-treasures)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.51s
     Running `target/debug/rust-island-treasures`
You found Golden Idol

It works! But… inside explore_cave, treasure owns the data on the heap. When the function ends, Rust calls drop automatically, freeing the memory. No manual cleanup, no garbage collector hunting it down later. That’s ownership rule #3 in action: when the owner goes out of scope, the value is dropped.

Now, what if we want to keep the treasure? Let’s try adding it to inventory outside the function.

fn main() {
    let mut inventory = String::from("Empty backpack");
    explore_cave(); // hmm... do we get the treasure out?
    println!("Inventory: {}", inventory);
}

fn explore_cave() {
    let treasure = String::from("Golden Idol");
    // can't return it yet... let's pretend we add it here later
}

Stuck? The treasure is owned by explore_cave and vanishes when the scope ends. We need to pass ownership back.

Level 2: Passing Ownership (Functions and Moves)

To “hand over” the treasure, we’ll return it from the function. This transfers ownership which follows rule #2: only one owner at a time.

Let’s update the code for this…

fn main() {
    let mut inventory = String::from("Empty backpack");
    let found_treasure = explore_cave(); // hmm... do we get the treasure out?
    inventory.push_str(&format!(" + {}", found_treasure));
    println!("Inventory: {}", inventory);
}

fn explore_cave() -> String {
    let treasure = String::from("Golden Idol");
    treasure // ownership moves to the caller
}

Well well well… let’s try cargo run on this again…

rust-island-treasures [ master][?][📦 v0.1.0][🦀 v1.88.0]
❯ cargo run
   Compiling rust-island-treasures v0.1.0 (/home/syk/dev/rndm/rust-island-treasures)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/rust-island-treasures`
Inventory: Empty backpack + Golden Idol

OH! It shows “Empty backpack + Golden Idol”. Looks like we managed to get it out of the cave! When we return treasure, ownership moves to found_treasure in main. The original treasure is invalidated; trying to use it after would make the compiler yell at us.

But… what about the integers or simple types? Let’s add a “score” to our game. Integers are stack-only so they copy instead of move.

READ ABOUT STACK-HEAP HERE IF YOU’RE A BOZO


fn main() {
    let mut inventory = String::from("Empty backpack");
    let found_treasure = explore_cave();
    inventory.push_str(&format!(" + {}", found_treasure));

    let score = calculate_score(10); // pass a stack value
    println!("Inventory: {} | Score: {}", inventory, score);
}

fn explore_cave() -> String {
    let treasure = String::from("Golden Idol");
    treasure
}

fn calculate_score(base: i32) -> i32 {
    base + 5 // copies base and original stays valid
}

Well what happened here… Integers implement Copy, so passing them doesn’t move ownership… they just duplicate on the stack. Super fast, no heap drama. Strings? They move because they’re heap-allocated and don’t implement Copy (they use Drop for cleanup).

And if we run this code after adding score part we get

rust-island-treasures [ master][?][📦 v0.1.0][🦀 v1.88.0]
❯ cargo run
   Compiling rust-island-treasures v0.1.0 (/home/syk/dev/rndm/rust-island-treasures)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/rust-island-treasures`
Inventory: Empty backpack + Golden Idol | Score: 15

We got our treasure and the score as well. We passed 10 and got 15.

Level 3: The Pitfall of Moves (Invalidating Variables)

Hmm let’s talk about the pitfalls of moves now. How about making this more game-like? Suppose we try to use a treasure after handing it off.

fn explore_cave() -> String {
    let treasure = String::from("Golden Idol");
    println!("Inspecting: {}", treasure); // used here
    treasure // move ownership out
}

This’ll work perfectly fine but…

fn explore_cave() -> String {
    let treasure = String::from("Golden Idol");
    let moved = treasure; // move to `moved`
    println!("Inspecting: {}", treasure); // Error! treasure is invalid now.
    moved
}

This’ll make the compiler yell at us… about what?

borrow of moved value: `treasure`
value borrowed here after move

Hmm… Ownership moved to moved, so treasure is toast. This prevents double-free bugs or dangling pointers… Rust catches it at compile time.

In our game, this is like dropping a treasure into a pit: once moved, it’s gone from our hands.

Boss Level: Cloning to Keep Multiples (Deep Copies)

What if we want to duplicate a treasure for trading? We can use clone() as it makes a deep copy of the heap data. Let’s see it in action using the following code:

fn main() {
    let mut inventory = String::from("Empty backpack");
    let found_treasure = explore_cave();
    let duplicate = found_treasure.clone(); // expensive but keep original data

    inventory.push_str(&format!(" + {} + Duplicate {}", found_treasure, duplicate));

    let score = calculate_score(10);
    println!("Inventory: {} | Score: {}", inventory, score);
}

fn explore_cave() -> String {
    let treasure = String::from("Golden Idol");
    treasure
}

fn calculate_score(base: i32) -> i32 {
    base + 5
}

Now inventory has both but clone() allocates a new heap space it’s not free. Use it sparingly, like in a game where duplicating costs “mana”.

Notice how stack vs heap matters? Stack pushes are instant (top of the stack), heap needs allocator searches. Ownership keeps heap tidy without GC pauses or manual frees.

Let’s try to run this updated code now

rust-island-treasures [ master][?][📦 v0.1.0][🦀 v1.88.0]
❯ cargo run
   Compiling rust-island-treasures v0.1.0 (/home/syk/dev/rndm/rust-island-treasures)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.40s
     Running `target/debug/rust-island-treasures`
Inventory: Empty backpack + Golden Idol + Duplicate Golden Idol | Score: 15

Awesome! We got a duplicate Golden Idol now. Which explains how cloning makes the difference here. It made a new copy of found_treasure into the heap.

And there you have it. A basic treasure hunt game teaching us ownership without feeling like a boring lecture (which I never experienced). Oh and what are the takeaways here?

  • One Owner Rule: Prevents chaos like multiple claims on the same treasure.
  • Moves: Transfer ownership, invalidate the old variable (great for heap safety).
  • Scope Drops: Auto-cleanup when things go out of scope and no leaks.
  • Copy vs Clone: Stack types copy cheaply; Heap needs explicit clones.

(If you’re following this then consider doing the homework below)

HOMEWORK: Run this full code. Tweak it. Add a user input for exploring more caves.

There’ll be a part 2 (if I got time) about references and borrowing: How to “borrow” a treasure without stealing it. Which is perfect for inspecting without losing ownership.