Learning Rust 🦀: 08 - Ownership: Functions and References
Tags: learning, rust, programming, development
Published: 2023-08-05 16:10:17

Now we will see how Rust's Ownership affects Functions behavior. Let's dig in! 

> Please check the previous 2 articles in this series to recap what we have discussed about Ownership. 

>#### ⚠️ Remember!
You can find all the code snippets for this series in its [accompanying repo](https://github.com/FadyGrAb/rust-learning-series)  
If you don't want to install Rust locally, you can play with all the code of this series in the official [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021) that can be found on its official page.

> ⚠️⚠️ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.

⭐ I try to publish a new article every week (maybe more if the Rust gods 🙌 are generous 😁) so stay tuned 😉. I'll be posting "new articles updates" on my [LinkedIn](https://www.linkedin.com/in/fady-abdelmassih) and [Twitter](https://twitter.com/fadyGrAb).

## Table of Contents:
- [Ownership and functions](#ownership-and-functions)
- [References and borrowing](#references-and-borrowing)
- [The rules of references](#the-rules-of-references)

### Ownership and functions:
Passing variables to functions behaves similarly to assigning values to variables. It either *copies* or *moves* the variable into the function's scope.  
```rust
fn main() {
    let s1 = String::from("string");
    take_scope(s1); // s1 is moved into 'take_scope'

    // Error: borrow of moved value: `s1`
    //println!("s1: {s1}")

    let n1 = 5;
    take_num(n1); // n1 is COPIED into 'take_num'
    println!("n1 from main: {n1}"); // this is fine as n1 is copied not moved.
}

fn take_scope(s: String) -> () {
    println!("s1 from take_scope {s}")
}

fn take_num(n: i32) -> () {
    println!("n: {n}")
}
``` 
Because `s1` is a *String* type, it will be moved to the 'take_scope' function. So, calling it again from the 'main' function will cause a compilation error. But for `n1` as it is of `i32` type (simple type), it will be copied not moved into the 'take_num' function so it's OK to call it again from the 'main' function.  

Functions can return *move back* the variables they receive if they are set as the return value.
```rust
fn main() {
    let s1 = String::from("String 2"); 
    let s2 = take_and_return_scope(s1); // s1 is moved to 'take_and_return_scope' then moved back into s2
    println!("S2: {s2}");
}

fn take_and_return_scope(s: String) -> String {
    s
}
```
The 'take_and_return_scope' function takes only one variable `s` and returns it. It is said that it's *moved* back into `s2`. But generally, functions also return *computed* values that are needed elsewhere in your app. So, in order to do that functions can return a *tuple* containing the original variable to be moved back into its original scope in addition to the calculated values.
```rust
fn main() {
    let s3 = String::from("String 3");
    let (s4, len) = return_length(s3); // 'return_length' moves back the original string value along with the computed value.
    println!("s4 is '{s4}', length is {len}");
}

fn return_length(s: String) -> (String, usize) {
    let length: usize = s.len();
    (s, length)
}
```
But it is very tedious and time consuming to design your functions to move back the passed variables. What if you have a large number of passed variables?! Your code will be difficult to maintain and organize. And what if we don't want to move the variable from its original scope?! Enter: References and Borrowing.

### References and borrowing:
Think of references as pointers to a piece of data (addresses) that can be used to refer to the data without changing ownership. This is called *borrowing* in Rust. We can re-write the tuple example from the last section as follows:
```rust
fn main() {
    let s1 = String::from("references");
    let len = return_length(&s1);
    println!("s1: '{s1}' and its length is {len}"); // won't error out as s1 wasn't moved into 'return_length'
}

fn return_length(s: &String) -> usize {
    s.len()
}
```
References are defined by `&` as we have passed a reference to `s1` to the 'return_length' function like this `return_length(&s1)`. Note that in the 'return_length' function signature, references must be defined too. References are immutable by default. So, the following code will fail:
```rust
fn main() {
    let word = String::from("Hello");
    add_hello(&word);
    println!("word: {word}");
}

fn add_hello(s: &String) {
    s.push_str(", World!");
}
```
To make this work, the variable must be mutable and its reference to usint the `mut` keyword:
```rust
fn main() {
    let mut word = String::from("Hello");
    add_hello(&mut word);
    println!("word: {word}");
}

fn add_hello(s: &mut String) {
    s.push_str(", World!");
}
```
You can create immutable references as much as you like as long as there aren't a mutable reference for this variable. Also you can't create more than one mutable reference for the same variable. Therefore the following will fail:
```rust
fn main() {
    
    let mut s = String::from("Mutable");
    let s1 = &mut s;
    // ERROR: cannot borrow `s` as mutable more than once at a time
    let s2 = &mut s;
    println!("s1: {s1}, s2 {s2}");


    let mut r = String::from("ref");
    let r1 = &r; // no problem
    let r2 = &r; // no problem
    // ERROR: cannot borrow `r` as mutable because it is also borrowed as immutable
    let r3 = &mut r; // problem
    println!("{r1} {r2} {r3}");
}
```
Which makes sense, as if you have referenced a variable as immutable, you don't want it to be changed mid-application (referenced as mutable). But, this can work if the immutable references go out-of-scope before the mutable reference. This allows variable mutations but in a very controlled manner.
```rust
fn main() {
    let mut x = String::from("new ref");
    let x1 = &x; // no problem
    let x2 = &x; // no problem
    println!("{x1}, {x2}");
    let x3 = &mut x; // It's OK now as x1 and x2 went out of scope.
    println!("{x3}");
}
```

### The rules of references:
To summarize, references follow the following two rules:
1. You can have either one mutable reference or multiple immutable references for the same variable. (Understanding scopes will make understanding this rule easier)
2. References must always be valid. Rust takes care of that as it never *deallocates* a reference as long as it is still in use.  

In this article we have discussed a very interesting feature of Rust, borrowing. In the next article we will build on that knowledge and explore the **Slice Type**. See you then 👋.