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:
- 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:
- 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:
- 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