Skip to content

Commit f86ab7d

Browse files
committed
Rewrite Borrowing & References
1 parent db03746 commit f86ab7d

File tree

6 files changed

+266
-167
lines changed

6 files changed

+266
-167
lines changed

content/en/docs/c2.borrowing.md

Lines changed: 155 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,120 +3,195 @@ title: Borrowing
33
slug: borrowing
44
---
55

6-
In real life applications, most of the times we have to pass variable bindings to other functions or assign them to other variable bindings. In this case, we are **referencing** the original binding; **borrow** the data of it.
6+
> 💭 When we discussed [Ownership](/docs/ownership/#ownership) we mentioned that, _"each piece of data has a single owner, and data is only scoped to its owner (💯 if it is not borrowed)."_
77
8-
## What is Borrowing?
8+
## Borrowing & References
99

10-
> [Borrow \(verb\)](https://github.com/nikomatsakis/rust-tutorials-keynote/blob/master/Ownership%20and%20Borrowing.pdf)
11-
> To receive something with the promise of returning it.
10+
In a general sense, borrowing is taking something with the promise of returning it. In Rust, borrowing is the act of creating a reference (via `&` or `&mut`) to a value without copying the data or taking ownership of it. This is quite similar to the concept of "passing by reference" in other languages.
1211

13-
## Shared & Mutable borrowings
14-
15-
⭐️ There are two types of Borrowing,
12+
```rust
13+
fn main() {
14+
let a = 3;
15+
let b = &a; // 💡 Referencing
16+
let c = *b; // 💡 Dereferencing
1617

17-
1. **Shared Borrowing** `(&T)`
18+
println!("{a} {b} {c}"); // 3 3 3
19+
}
1820

19-
* A piece of data can be **borrowed by a single or multiple users**, but **data should not be altered**.
21+
// `a` has the ownership of 3. `b` is a reference to the data of `a`.
22+
// `c` is a separate variable/ separate ownership, created by copying the data of `a`.
23+
// ⭐️ `let c = *b;` is similar to `let c = a;`, we just use dereferenced reference to get the data.
24+
// 💯 `let c = *b;` is only possible when `a` is a Copy type. If `a` is not Copy type, we will get a compiler error.
25+
```
2026

21-
2. **Mutable Borrowing** `(&mut T)`
27+
In Rust, a reference cannot outlive the value it borrows. This kind of reference is called a "dangling reference" which occurs when a program tries to access memory that has already been deallocated.
2228

23-
* A piece of data can be **borrowed and altered by a single user**, but the data should not be accessible for any other users at that time.
29+
```rust
30+
fn main() {
31+
let a: &f64;
2432

25-
## Rules for borrowings
33+
{
34+
let b = 3.14159; // 💡b owns the data and b is scoped inside the { } block
35+
a = &b;
36+
} // 💡b is dropped here along with its allocated data; Rust doesn't allow us to refer to data after it is dropped
2637

27-
There are very important rules regarding borrowing,
38+
println!("{a}"); // ❌ Compile-time error: "b does not live long enough"
39+
}
40+
```
2841

29-
1. One piece of data can be borrowed **either** as a shared borrow **or** as a mutable borrow **at a given time. But not both at the same time**.
42+
The Rust Borrow Checker identifies these errors at compile time.
3043

31-
2. Borrowing **applies for both copy types and move types**.
44+
```rust
45+
5 | let b = 3.14159;
46+
| -- binding `b` declared here
47+
6 | a = &b;
48+
| ^^ borrowed value does not live long enough
49+
7 | }
50+
| - `b` dropped here while still borrowed
51+
8 |
52+
9 | println!("{a}");
53+
| - borrow later used here
54+
```
3255

33-
3. The concept of **Liveness**
56+
References are pointers that are either a thin pointer that points to a Sized type or a fat pointer which points to an Unsized type.
3457

3558
```rust
36-
fn main() {
37-
let mut a = vec![1, 2, 3];
38-
let b = &mut a; // &mut borrow of `a` starts here
39-
//
40-
// some code // ⁝
41-
// some code // ⁝
42-
} // &mut borrow of `a` ends here
43-
59+
use std::mem::size_of_val;
4460

4561
fn main() {
46-
let mut a = vec![1, 2, 3];
47-
let b = &mut a; // &mut borrow of `a` starts here
48-
// some code
49-
50-
println!("{:?}", a); // trying to access `a` as a shared borrow, so giving an error
51-
} // &mut borrow of `a` ends here
62+
let a = 3; // 💡 3 store on the stack
63+
let b: [i32; 3] = [1, 2, 3]; // 💡 [1, 2, 3] store inline on the stack
5264

65+
let c = &a; // 💡 c is a thin pointer (&i32) - [ptr]
66+
let d = &b; // 💡 d is also a thin pointer (&[i32; 3]) - [ptr]
67+
let e = &b[1..]; // 💡 [2, 3]; e is a fat pointer (&[i32]) - [ptr, len]
5368

54-
fn main() {
55-
let mut a = vec![1, 2, 3];
56-
{
57-
let b = &mut a; // &mut borrow of `a` starts here
58-
// any other code
59-
} // &mut borrow of `a` ends here
69+
println!("\nSize of actual data: a: {} bytes, b: {} bytes",
70+
size_of_val(&a), size_of_val(&b) // 4 and 12
71+
);
6072

61-
println!("{:?}", a); // allow borrowing `a` as a shared borrow
73+
println!("Size of the references: c: {} bytes, d: {} bytes, e: {} bytes",
74+
size_of_val(&c), size_of_val(&d), size_of_val(&e) // 8 , 8, 16
75+
);
6276
}
6377
```
6478

65-
💡 Let’s see how to use shared and mutable borrowings in examples.
79+
> 💡 An array of Copy types stores its data inline (store elements contiguously) wherever it is declared. It lives on the stack as a local variable, in the binary if it is `static`, or on the heap if it is wrapped in a `Box` or `Vec`.
80+
>
81+
> ```rust
82+
> STACK
83+
> [ 1, 2, 3 ] // let b: [i32; 3] = [1, 2, 3];
84+
>
85+
> [ ptr ] // let d = &b; 💡 pointer points to 0th index element of the array + len(3) is baked into the Type
86+
> (Thin Ptr)
87+
>
88+
> [ ptr, len ] // let e = &b[1..]; 💡 pointer points to 1st index element of the array
89+
> (Fat Ptr)
90+
> ```
91+
>
92+
> 💡 An array of non-Copy types also stores its elements inline and contiguously wherever it is declared. However, each element acts as a "header" that contains a pointer to the actual data stored on the heap. Example: `[String; 3]`
93+
>
94+
> ```rust
95+
> STACK HEAP
96+
> [ String, String, String ] -> "A", "B", "C" // if `let b = [String::from("A"), String::from("B"), String::from("C")];`
97+
> 3x ( ptr, len, cap) (3 allocations)
98+
>
99+
> [ ptr ] // let d = &b; 💡 pointer points to 0th String Header
100+
> (Thin Ptr)
101+
>
102+
> [ ptr, len ] // let e = &b[1..]; 💡 pointer points to 1st String Header
103+
> (Fat Ptr)
104+
> ```
66105
67-
### Examples for Shared Borrowing
106+
References are immutable by default (called shared references) but can be made mutable with the `mut` keyword.
68107
69-
```rust
70-
fn main() {
71-
let a = [1, 2, 3];
72-
let b = &a;
73-
println!("{:?} {}", a, b[0]); // [1, 2, 3] 1
74-
}
108+
- Shared references/ `&T` : Copy types and only copy the pointer (thin or fat), not actual data.
109+
- Mutable references: `&mut T` : Non-copy types but reborrowing is allowed (A temporary reference to the same data with a shorter lifetime)
75110
111+
Because Rust is built for memory safety, we are not allowed to create a mutable reference or mutate data while having any active shared reference. Additionally, we are not allowed to have multiple active mutable references to the same piece of data at once. Rust borrow checker identifies these errors at compile time to prevent data races and undefined behavior.
76112
77-
fn main() {
78-
let a = vec![1, 2, 3];
79-
let b = get_first_element(&a);
113+
## Shared References / Immutable References / `(&T)`
80114
81-
println!("{:?} {}", a, b); // [1, 2, 3] 1
82-
}
115+
- The `println!()` calls with `{}` or `{:?}` create new shared references to the data.
83116
84-
fn get_first_element(a: &Vec<i32>) -> i32 {
85-
a[0]
86-
}
87-
```
117+
```rust
118+
fn main() {
119+
let a = 128;
120+
let b = String::from("ABC");
88121
89-
### Examples for Mutable Borrowing
122+
println!("{a}"); // 💡 A shared reference
123+
println!("{b}"); // 💡 A shared reference
124+
}
125+
```
90126
91-
```rust
92-
fn main() {
93-
let mut a = [1, 2, 3];
94-
let b = &mut a;
95-
b[0] = 4;
96-
println!("{:?}", b); // [4, 2, 3]
97-
}
127+
- The `&` operator creates a new shared reference to the data.
98128

129+
```rust
130+
fn main() {
131+
let a = String::from("ABCDE");
99132

100-
fn main() {
101-
let mut a = [1, 2, 3];
102-
{
103-
let b = &mut a;
104-
b[0] = 4;
105-
}
133+
println!("{a}"); // 💡 A shared reference
106134

107-
println!("{:?}", a); // [4, 2, 3]
108-
}
135+
borrow_and_print(&a); // 💡 A shared reference
109136

137+
let b = &a; // 💡 A shared reference
138+
borrow_and_print(b);
139+
}
110140

111-
fn main() {
112-
let mut a = vec![1, 2, 3];
113-
let b = change_and_get_first_element(&mut a);
141+
fn borrow_and_print(a: &String) {
142+
println!("{a}");
143+
}
144+
```
114145

115-
println!("{:?} {}", a, b); // [4, 2, 3] 4
116-
}
146+
## Mutable references / `(&mut T)`
117147

118-
fn change_and_get_first_element(a: &mut Vec<i32>) -> i32 {
119-
a[0] = 4;
120-
a[0]
121-
}
122-
```
148+
A borrow doesn't necessarily last until the end of the curly braces `{ }`. Instead, it lasts only until its last use.
149+
150+
- The `&mut` operator creates a new mutable reference to the data. When passing a `&mut` to a function, the reference is scoped to that function call.
151+
152+
```rust
153+
fn main() {
154+
let mut a = String::from("AAA");
155+
156+
println!("{a}");
157+
mutate_and_print(&mut a); // 💡 A mutable reference; AAA-BBB
158+
mutate_and_print(&mut a); // 💡 A mutable reference; AAA-BBB-BBB
159+
160+
let b = &mut a; // 💡 A mutable reference; AAA-BBB-BBB-CCC while printing
161+
b.push_str("-CCC");
162+
println!("{a}");
163+
164+
mutate_and_print(&mut a); // 💡 A mutable reference; AAA-BBB-BBB-CCC-BBB
165+
}
166+
167+
fn mutate_and_print(a: &mut String) {
168+
a.push_str("-BBB");
169+
println!("{a}");
170+
}
171+
172+
// Eventhough `let b = &mut a;` creates a new mutable reference, its last use only till the next line. So, the sahred reference creation via `println!()` is still valid and trigger no error.
173+
```
174+
175+
- Shared references are Copy types while mutable references are Move types. However, when we pass a mutable reference to a function or assign it to a new variable with an explicit type, Rust doesn't move the original reference. Instead, Rust creates a new temporary reference to the same data with a shorter lifetime. This is called "reborrowing" as it's a "borrow from a borrow". While this new reborrow is active, the original reference cannot be used.
176+
177+
```rust
178+
fn main() {
179+
let mut a = 128;
180+
let b = &mut a; // 💡 First mutable reference
181+
182+
{
183+
let c: &mut i32 = b; // 💡 Reborrowing; A temporary mutable reference
184+
*c += 1;
185+
186+
// println!("{b}"); // cannot borrow `b` as immutable because it is also borrowed as mutable for `c`
187+
println!("{c}");
188+
}
189+
190+
*b += 1;
191+
println!("{a}");
192+
}
193+
```
194+
195+
## 👨‍🏫 Before going to the next...
196+
197+
- 💯 Check `Rc<T>`, `Arc<T>`, `Cell<T>`, `RefCell<T>` types and interior mutability.

0 commit comments

Comments
 (0)