Learn the concept of ownership using these simple step by step examples.

Example 1: Rust Scope

Learn scope in Rust through simple examples.

This example project will teach you the following concepts:

  1. Ownership rules with scope.

Step 1: Create Project

The first step is to create a Rust project. We can use Cargo to generate us a project template. Use the following command:

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

Here’s the code:

main.rs

// OWNERSHIP
//
// MAIN IDEA:
//
// "SCOPE owns a VARIABLE owns a VALUE"
//
// + A value lives on memory.
// + It can move between variables.
// + A variable can be an owner of a value.
// + A variable comes in and goes out of a scope.
//
// RULES:
// + Each value has a single owner.
// + Ownership can move between variables.
// + When the owner goes out of scope, the value will be dropped.

fn main() {
    scope();
}

fn scope() {            // s isn't declared yet. not valid here.
    let s = "hi";       // s comes into scope here.

    println!("{}", s);  // work with s however you want.

}                       // s goes out of scope.
                        // you can no longer use it.

Run

Save the code and run your code as follows using Cargo:

$ cargo run

Result

If you run the code you will get:

hi

Example 2: Simple string example

Learn strings in Rust via simple examples.

This example project will teach you the following concepts:

  1. strings in rust.

Step 1: Create Project

Create a Rust project using Cargo.

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

Here’s the code:

main.rs

// "hi earthlings"
// -> This is a string literal.
// -> Its value is known at compile-time.
// -> Rust hardcodes it into the final executable.
//
// String::from("hi");
// -> This is a String.
// -> Its value is only known in runtime.
// -> It can dynamically change in runtime.
// -> Rust allocates it on the HEAP memory.

// For heap allocated values, Rust needs to manage
// them somehow. When they're no longer needed,
// it should clean them from memory (to drop'em).
fn main() {
    let mut s = String::from("hi");   // s comes into the scope
                                      // its data is allocated on the heap

    s.push_str(", earthlings!");      // change the s

    println!("{}", s);                // print the s

}                                     // s goes out of scope
                                      // Rust calls the drop fn on s
                                      // that returns the memory used by s
                                      // back to OS

Run

Save the code and run your code as follows using Cargo:

$ cargo run

Result

If you run the code you will get:

hi, earthlings!

Example 3: Move Variable in Rust

How to move a variable’s value to another variable without copying.

Step 1: Create Project

Create a Rust project using Cargo.

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

Replace your main.rs with the following code:

main.rs

// Variables have two type of semantics:
//
// 1. Move Semantics:
//
//    -> Variable's value moves to another variable
//       without copying.
//
//    -> Used for heap-allocated values like a String.
//
//
// 2. Copy Semantics:
//
//    -> Rust copies the variable's value, and uses
//       the new value for the new variable.
//
//    -> Used for scalar values like integers.
//
//    -> This kind of values usually live on stack.
//
// SEE: https://doc.rust-lang.org/std/marker/trait.Copy.html
fn main() {
    // =======================================
    // STACK MEMORY
    // =======================================

    // -> 5 can be allocated on the stack memory.
    // -> It's a simpler scalar value.
    // -> So Rust can copy it cheaply.
    let x = 5;

    // WHAT HAPPENS BELOW?
    //
    // 1. Rust COPIES x's value: 5.
    // 2. And BINDS the new value to the y variable.
    // 3. What the y contains is a copy.
    let y = x;
    // Here x and y have different memory locations.
    println!("x: {} y: {}", x, y);

    // =======================================
    // HEAP MEMORY
    // =======================================

    // WHAT IS A STRING?
    //
    // String::from("hi") looks like this:
    //
    // This part is usually       But this part should
    // allocated on the stack.    live on the heap.
    //
    // --------+---------         ----- + -----
    // name    | value            index | value
    // --------+---------         ----- + -----
    //  ptr    |  0x01  ------->    0       h
    //  len    |  2                 1       i
    //  cap    |  2               ----- + -----
    // --------+---------           ^
    //                              |
    //                              +-------+
    //                                      |
    // s1 contains the String value below.  |
    let s1 = String::from("hi"); //         |
    //                                      |
    // let s2 = s1;              //         | <-- READ THE CODE
    //                                      |
    // -> s2 is a new String value.         |
    // its ptr points to the same location  |
    // on the heap memory.                  |
    //                                      |
    // --------+---------                   |
    // name    | value                      |
    // --------+---------                   |
    //  ptr    |  0x01  --------------------+
    //  len    |  2
    //  cap    |  2
    // --------+---------

    //
    // THE CODE BELOW WON'T WORK.
    //
    // println!("{} {}", s1, s2);

    //
    // WHY?
    //
    // -> Rust moves s1's value to s2.
    // -> s2 is the new OWNER of s1's value.
    // -> s1 is no longer valid.
    //    -> goes out of scope.
    //    -> rust claims its memory.

    //
    // WHAT WOULD WORK??
    //
    let s2 = s1.clone();           // expensive op
    println!("{} {}", s1, s2);
    // WHY?
    // -> s2 has a deep-copy of s1's value.
    // -> there are one more "hi" on the heap now.
    // -> and its owner is s2.

    //
    // BUT IT WAS WORKING WITH AN INTEGER LITERAL BEFORE?
    // (IN THE STACK MEMORY SECTION ABOVE)
    //
    // BUT WHY? TELL ME MORE:
    //
    // 1. Simple values like an integer doesn't need to be cloned.
    // 2. They can be copied by Rust automatically.
    // 3. It has a Copy trait.
    //    SEE: https://doc.rust-lang.org/std/marker/trait.Copy.html
    //
}

Run

Save the code and run your code as follows using Cargo:

$ cargo run

If you run the code you will get:

x: 5 y: 5
hi hi

Example 4: Changing and Giving Ownership

Another ownership example.

Step 1: Create Project

Create a Rust project using Cargo.

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

// Passing a value to a func is similar to assigning it to a variable.
// It will either MOVE or COPY the value.
fn main() {
    let mut s = String::from("hi");

    change_owner(s);                    // s's value (its pointer) moves
                                        //       into the change_owner()
                                        //
                                        // s becomes invalid
                                        // -> rust has reclaimed its memory in
                                        //                  the change_owner()
    let n = 5;
    copy(n);                            // rust copies n (nothing moves)
    println!("{}", n);                  // -> so we can still use it

    // println!("{}", s);               // but we can't use s
                                        // -> it was moved to the change_owner()

    s = give_owner();                   // -> s owns give_owner's s's value now
                                        // -> s is valid now
    let s2 = String::from("hello");
    let s3 = change_and_give_owner(s2); // -> s2 looses ownership to the func
                                        // -> and the func gives it back to s3

    // s2 is no longer valid:
    // println!("s: {} s2: {} s3: {}", s, s2, s3);
    println!("s: {} s3: {}", s, s3);

}   // out of scope: s and s3 are dropped.

fn change_owner(s: String) {            // main's s's new owner is this func
    println!("{}", s);
}                                       // out of scope: s is dropped
                                        // rust reclaims its memory

fn copy(i: i32) {                       // receives a copy of n as i
    println!("{}", i);
}

fn give_owner() -> String {
    let s = String::from("hey");        // s is the owner of String("hey")

    s                                   // give_owner loses the ownership to
                                        // the calling func.
                                        // -> s's value will live in the calling func.
}

fn change_and_give_owner(s: String) -> String {  // becomes the owner of s
    s                                            // loses the ownership of s to
                                                 // the calling func.
}

Run

Save the code and run your code as follows using Cargo:

$ cargo run

Result

Run it and you will get the following:

hi
5
5
s: hey s3: hello

Example 5: Rust References

This example project will teach you the following concepts:

  1. Concept of references in Rust

Step 1: Create Project

Create a Rust project using Cargo.

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

Replace your main.rs with the following code:

main.rs

//
// WHAT IS A REFERENCE?
//
// It's a value that refers to another value
// without taking its ownership.
//
// Represented with a leading ampersand &.
//
//      &s          let s = String::from("hi")       "hi"
//
// name | value           name | value          index | value
// ---- + ------          ---- + ------         ----- + -----
// ptr  |  0x5  ------->  ptr  |  0x01  ------->  0   |   h
//                        len  |  2               1   |   i
//                        cap  |  2             ----- + -----
//                        -----+-------
//
fn main() {
    let s = String::from("hi");

    let l = strlen(&s);              // strlen BORROWS s.
                                     // strlen doesn't own it.
                                     // main is the OWNER.
                                     // strlen is the BORROWER.

    println!("len({}) = {}", s, l);  // that's why we can still use s here.

    //

    let mut cs = s;                  // cs is the owner of s now.

    change(&mut cs);                 // send a mutable ref of cs with change()
                                     // -> so change() can change it.

    println!("{}", cs);              // we can still use cs because
                                     // we're the owner of it.
    println!("{:?}", cs);

    // ========================================================================
    // MULTIPLE MUTABLE REFERENCES
    //
    // let mut s = String::from("hey");
    //
    // IN THE SAME SCOPE:
    //
    // -> There can be only a single mutable borrower (reference).
    // {
    //     let mutRefToS = &mut s;
    //
    // }
    // mutRefToS goes out of scope, Rust drops it.
    // That's why you can make a new reference to it here.
    //
    // let mutRef2ToS = &mut s;
    //
    // -> There can be multiple non-mutable borrowers.
    // let rs1 = &s;     // immutable borrower
    // let rs2 = &s;     // immutable borrower
    //
    // -> There can't be a mutable borrower if there are immutable borrowers.
    // let rs3 = &mut s; // mutable borrower
    // println!("{} {} {}", rs1, rs2, rs3);
    //
    // ========================================================================

}   // the main is the owner of s, and cs.
    // they go out of scope and but Rust drops them.

fn strlen(s: &String) -> usize {     // s is a reference to a String
    s.len()
}   // s goes out of scope but nothing happens.
    // because strlen isn't the owner of s,
    // the main() is.

/*
this won't work.
s is not a mutable reference.
fn change(s: &String) {
    s.push_str(" there!");
}
*/
fn change(s: &mut String) {
    s.push_str(" there!");
}

Run

Save the code and run your code as follows using Cargo:

$ cargo run

Result

If you run it you will get the following:

len(hi) = 2
hi there!
"hi there!"

Example 6: Dangling References

Yet another references example.

Step 1: Create Project

Create a Rust project using Cargo.

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

Here’s the code:

main.rs

// Dangling References
// Rust never gives you a dangling pointer.

fn main() {
    // let refToNothing = dangle();
    // -> think about using the null pointer here! CRASH!
}

/*
fn dangle() -> &String {            // returns a ref to a String
    let s = String::from("hello");
    &s                              // return a ref to s
}                                   // but s drops!
*/

fn _safe() -> String {
    let s = String::from("hello");
    s
}

Example 7: Slices

Start by creating a project.

Step 1: Create Project

Create a rust project:

cargo new your_project_name

Step 2: Dependencies

No dependencies are needed for this project.

Step 3: Write Code

main.rs

fn main() {
    // ========================================================================
    // ARRAYS
    // -> An array is a contiguous collection of objects of the same type: T.
    // -> The compiler knows an array's size at compile-time.
    // -> Arrays are stack allocated.
    //
    // You can declare an array like this:
    //
    //      let nums: [i32; 3] = [1, 2, 3];
    //                  ^   ^     ^
    //                  |   |     +---------------+
    //                  |   +--------+            |
    //           Type of elements    |            |
    //                        Array's length      |
    //                                     Array's Elements
    //

    // ========================================================================
    // SLICES
    // -> A slice is a reference to a part of a contiguous sequence of
    //    elements.
    // -> It's actually a two-word object:
    //    -> 1st word: A pointer to the original data.
    //    -> 2nd word: The length of the slice.
    //
    // For example:
    // -> A slice can borrow a portion of the array: nums.
    //
    //      let borrowed_array = &nums;
    //          ^
    //          |
    //        &[i32]
    //     an i32 slice
    //
    //

    // ========================================================================
    // A STRING SLICE EXAMPLE
    //
    //      let s = String::from("hello"); // s is a string slice

    // ========================================================================
    // SLICING
    //
    //      [starting_index..ending_index]
    //
    //      &s[0..len(s)]      // hello
    //      &s[1..3]           // el
    //      &s[0..]            // hello
    //      &s[..len(s)]       // hello
    //      &s[..]             // hello
    //

    let s = String::from("they were walking down the street");
    let word = first_word(&s);
    println!("{}", word);

    // ========================================================================
    // string literals are slices.
    //
    // we can pass them directly to an fn if it accepts
    // a string slice.
    println!("{}", first_word("hello world"));

    // ========================================================================
    // ERROR
    // String slice range indices should respect UTF-8 character boundaries.
    //
    // let s = String::from("yolun aşağısına doğru yürüyorlardı");
    // println!("{}", &s[6..8]);

    // ========================================================================
    // If you could borrow immutably and mutably in the same scope, errors like
    // below could happen. Rust doesn't allow this to happen.
    //
    // Comment out to see the error.
    //
    // let mut s = String::from("they were walking down the street");
    // let word = first_word(&s);    // get a slice to the first word portion of s
                                     //
                                     // &s means an immutable borrow
                                     //
    // s.clear();                    // err -> can't clear s because clear needs
                                     // to borrow s mutably.
                                     //
    // println!("{}", word);         // could print a non-existent word!..
}

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }
    &s[..]
}
// -> As in the first_word() above,
//    It's better to accept a string slice
//    instead of a String reference.
//
// -> This way, first_word can accept both a String ref,
//    as well as a string literal, and so on.
//
fn _use_first_word() {
    let s = String::from("hey");
    let _ = first_word(&s[..]);  // pass the whole s

    let l = "hello";             // a string literal
    let _ = first_word(&l[..]);  // pass the string literal as a slice.
    let _ = first_word(l);       // <- or simply pass itself.
                                 // works because string literals == slices
}

Run

Save the code and run your code as follows using Cargo:

$ cargo run

Result

You will get the following:

they
hello

Categorized in: