DEV Community

Cover image for Alice, Bob, and Carol Take Ownership
Pan Chasinga
Pan Chasinga

Posted on

Alice, Bob, and Carol Take Ownership

A thought exercise in Rust ownership system for noobs.

Chapter 1: Move


let alice = String::from("happy");
let bob = alice;

// is alice still happy? NO.
// alice is now invalid.
// bob now has alice's livelihood.
println!("alice is {}", alice);

Enter fullscreen mode Exit fullscreen mode

In Rust, a value can always have only one owner.

Let's return the happiness to alice.


// ERROR!
alice = bob;

Enter fullscreen mode Exit fullscreen mode

All variables are immutable by default.

Therefore, alice will be sad forever.

Then carol comes along and asks bob if she could also be happy. bob, sunken in regret of killing alice, agree to try.


fn carol(v: String) {
    println("carol is {}", v)
}

fn main() {
    let alice = String::from("happy");
    let bob = alice;

    carol(bob);

    // bob is dead.
    println!("bob is {}", bob);

    // so is alice
    println!("alice is {}", alice);
}

Enter fullscreen mode Exit fullscreen mode

carol, realizing the hard truth of life, stand holding happy until the end of time (or lifetime of main).

"happy" String was moved from alice to bob, then to carol.

Chapter 2: Copy


let alice = "happy";
let bob = alice;

// Both can be happy!
println!("bob is {}", bob);
println!("alice is {}", alice);


Enter fullscreen mode Exit fullscreen mode

Because Rust knows how big "happy", 1, -20, 0x08, 3.1418 are beforehand, it simply creates a new copy for everyone.

Simple fixed size types are not moved; They are copied.

Chapter 3: Clone

In Chapter 1, if both alice and bob were to be happy, bob could have cloned alice's happiness.


let alice = String::from("happy");
let bob = alice.clone();

println!("bob is {}", bob);
// alice is still alive and well.
println!("alice is {}", alice);

Enter fullscreen mode Exit fullscreen mode

When something is cloned, it summons an evil doppelganger from the computer's limbo called the Heap. If the thing is huge, it means twice the memory and time.

It is costly to make everyone happy.

Chapter 4: Borrowing

In Chapter 1, what if carol could have turned back time and chosen to only take happy from bob for a while, and return it to him?


let alice = String::from("happy");

// happiness moves from alice to bob.
// bob is also mutable so he can later
// get his `happy` back from carol.
let mut bob = alice;

fn carol(v: String) -> String {
    // carol enjoys the happiness
    println!("carol is {}", v);
    // then returns it.
    v
}

bob = carol(bob);

// bob is fine and dandy
println!("bob is {}", bob);

Enter fullscreen mode Exit fullscreen mode

carol has to return the moved happiness so somebody else can use.

But instead of always returning, which is forget-prone, carol could just borrow.

First, let's prove that when something is moved, it is gone forever, even after the last owner is dead.


let alice = String::from("happy");

// New scope
{
    let bob = alice;

    // bob owns happiness.
    println!("bob is {}", bob);

    // bob is going out of scope.
}

// ERROR! alice is invalid forever.
println!("alice is {}", alice);

Enter fullscreen mode Exit fullscreen mode

To settle for a middle ground, bob borrows from alice instead.


let alice = String::from("happy");
{
    // bob borrows from alice.
    // alice still owns the happiness.
    // She just can't use it now.
    let bob = &alice;

    // bob enjoys happiness.
    println!("bob is {}", bob);

    // ERROR! alice tries to use happy
    println!("alice is {}", alice);

    // bob is out of scope, returning what
    // he borrowed automatically.
}

// alice's livelihood is back :).
println!("alice is {}", alice);

Enter fullscreen mode Exit fullscreen mode

carol decides she better borrow from bob while he is at it.


fn carol(v: &String) {
    // carol enjoys the happiness she borrowed.
    println!("carol is {}", v);
    // carol is going out of scope.
} 

fn main() {
    let alice = String::from("happy");
    {
        let bob = &alice;

        // bob enjoys happiness.
        println!("bob is {}", bob);

        // while carol is borrowing, bob has to wait.
        // carol does not have to return ownership back to bob.
        carol(&bob)

        // bob now has the happiness because carol is done.
        println!("bob is {} again", bob);

        // bob is out of scope.
     }

     // alice can now be happy again, after bob and carol are done.
     println!("alice is {}", alice);
}

Enter fullscreen mode Exit fullscreen mode

Anyone can borrow taking a reference (&) to a thing, but the lender must outlive the borrower.

Chapter 6: Lifetime

In Rust, a borrowed reference has a lifetime.


fn carol<'carol_t>(v: &'carol_t String) {
    // 'carol_t is a lifetime of carol.
    // This reads "carol has a lifetime `carol_t`, 
    // and she will only borrow `v` that lives at 
    // least `carol_t` long.
}

Enter fullscreen mode Exit fullscreen mode

A lifetime is the time and scope of when a referenced value is created until the end.

carol is hopeless at her pursue of happy-ness. So she gets into the loan business.

She creates an account with money inside and gives out to anyone who wants to borrow from her.

She uses reference so whoever borrows from her will always return the money when he/she dies.


// ERROR! 
fn carol() -> &i32 {
   let carol_reserve = 10;
   &carol_reserve
}

Enter fullscreen mode Exit fullscreen mode

Alas, because carol_reserve has a short lifetime inside carol, it cannot be borrowed from anyone. :(


// With lifetime annotation revealed.
fn carol<'carol_t>() -> &'carol_t i32 {
    let carol_reserve: 'carol_t i32 = 25;
    let carol_reserve_ref: &'carol_t i32 = &carol_reserve;
    carol_reserve_ref

    // But 'carol_t finishes here.
}

// let's say `main` has a lifetime named `main_t`.
fn main<'main_t>() {

    // It is impossible for `broke_bob` to borrow
    // from `carol()` because he lives longer than
    // `carol_t`.
    // (He lives slightly after `main_t` and dies just before)
    let broke_bob<'bob_t> = &carol<'carol_t>();
}

Enter fullscreen mode Exit fullscreen mode

The only way carol could have pulled this off is to borrow the amount from somewhere that lives longer than herself (carol_t) and broke_bob (bob_t).


// `CENTRAL_RESERVE` lives for the entire lifetime
// of a program.
static CENTRAL_RESERVE: i32 = 1000_000_000;

fn carol(money: &i32) -> &i32 {
    money
}

fn main() {
    broke_bob = carol(&CENTRAL_RESERVE);
}

Enter fullscreen mode Exit fullscreen mode

The main point of lifetimes is to make sure a reference outlives whoever borrows it.


If you're interested in my non-programming thoughts, you can subscribe to my newsletter BETA School.

Joe Chasinga Newsletter BETA School

Top comments (0)