aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSebastiano Tronto <sebastiano@tronto.net>2025-06-26 11:43:41 +0200
committerSebastiano Tronto <sebastiano@tronto.net>2025-06-26 11:43:41 +0200
commitc225088f94f6efa3a6f670011596691b862448f4 (patch)
tree7f95bf381c1c585bea3244c7e160b3e9acfb1f6f /src
parent5fe4b5bce18ede354b29620bbdfff179300710f1 (diff)
downloadsebastiano.tronto.net-c225088f94f6efa3a6f670011596691b862448f4.tar.gz
sebastiano.tronto.net-c225088f94f6efa3a6f670011596691b862448f4.zip
Blog post
Diffstat (limited to 'src')
-rw-r--r--src/blog/2025-06-26-borrow-checker/borrow-checker.md150
1 files changed, 150 insertions, 0 deletions
diff --git a/src/blog/2025-06-26-borrow-checker/borrow-checker.md b/src/blog/2025-06-26-borrow-checker/borrow-checker.md
new file mode 100644
index 0000000..b22d741
--- /dev/null
+++ b/src/blog/2025-06-26-borrow-checker/borrow-checker.md
@@ -0,0 +1,150 @@
1# Stunned by the borrow checker 🦀
2
3As I mentioned in my [last post](../2025-06-13-cargo-culture-shock),
4in the last couple of weeks I have been learning Rust. I have written
5[a small library](https://git.tronto.net/zmodn-rs/file/README.md.html) for
6[integers modulo N](https://en.wikipedia.org/wiki/Modular_arithmetic)
7(the original C++ version was mentioned in
8[this post](../2025-01-21-taming-cpp-templates), rewritten
9[my implementation](https://git.tronto.net/ecm/file/README.md.html)
10of the
11[ECM algorithm](https://en.wikipedia.org/wiki/Lenstra_elliptic-curve_factorization)
12(mentioned in [this other post](../2025-02-27-elliptic-curves-javascript))
13and I am now playing around with some past
14[Advent of Code](https://adventofcode.com/) problems.
15
16But today I won't talk about any of the above. Instead, I just want to show
17you a small example of code that kept me confused for a couple of hours.
18
19First, I need to very briefly explain Rust's concept of ownership.
20
21## The borrow checker
22
23The *borrow checker* is a unique feature of Rust that prevents certain
24kinds of memory errors and data races. Without going into too much
25detail, the borrow checker is a compile-time mechanism that ensures that,
26at any given point, a given object is owned by at most one reference,
27unless all references to it are *immutable* (that is, they don't allow
28modifying the object).
29
30As a simple example, the following code is not valid:
31
32```
33fn main() {
34 let mut x = 2; // mut means mutable, without it x would be a constant
35 let y = &x; // & means reference
36 x = 3;
37 println!("{x} {y}");
38}
39```
40
41And the compiler gives a clear explanation:
42
43```
44error[E0506]: cannot assign to `x` because it is borrowed
45 --> t.rs:4:5
46 |
473 | let y = &x;
48 | -- `x` is borrowed here
494 | x = 3;
50 | ^^^^^ `x` is assigned to here but it was already borrowed
515 | println!("{x} {y}");
52 | --- borrow later used here
53```
54
55What happens is that creating a (mutable) reference `y` that refers to
56`x`, *borrows* the object referred to by the name `x`, so `x` cannot be
57used directly anymore until `y` goes out of scope.
58
59If you want to know more, check out the
60[ownership chapter in the book](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html).
61
62## A tricky example
63
64Let's say we have a vector of vectors, and we want to copy an element from
65one of the internal vectors to another. We could try something like this:
66
67```
68fn main() {
69 let mut v = vec![vec![23], vec![42]]; // v is now {{23}, {42}}
70 v[0].push(v[1][0]);
71}
72```
73
74But this won't compile. Indeed we get:
75
76```
77error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
78 --> a.rs:3:15
79 |
803 | v[0].push(v[1][0]);
81 | - ---- ^ immutable borrow occurs here
82 | | |
83 | | mutable borrow later used by call
84 | mutable borrow occurs here
85 |
86 = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
87```
88
89However, the following works just fine:
90
91```
92fn main() {
93 let mut v = vec![vec![23], vec![42]];
94 let x = v[1][0];
95 v[0].push(x);
96}
97```
98
99And at this point I was very confused. Whatever the borrow checker does,
100shouldn't the two pieces of code do exactly the same? I got stuck for
101a while thinking that for some reason the function argument of `push()`
102was passed by reference in the first case, while it was copied in the
103second, but this is not where the problem lies.
104
105I was finally able to understand the problem when I realized that my first
106piece of code is equivalent to the following, which also does not compile:
107
108```
109fn main() {
110 let mut v = vec![vec![23], vec![42]];
111 let mut v0 = &mut v[0];
112 let x = v[1][0];
113 v0.push(x);
114}
115```
116
117Can you see the issue now?
118
119## Explanation
120
121Like in C++ and many other languages, the square
122bracket operator is a method on the vector object.
123More precisely, in Rust it is syntactic sugar for either the
124[`index()`](https://doc.rust-lang.org/std/ops/trait.Index.html) or the
125[`index_mut()`](https://doc.rust-lang.org/std/ops/trait.IndexMut.html)
126functions, depending if mutability is requested in our usage. In our
127example, when we call `v[0].push(...)` this will be translated to a call
128to `index_mut()`, because `push()` requires a mutable reference; when
129we do e.g. `let x = v[1][0]`, the immutable version will be call instead.
130
131But the details of `[]` are not important for us. The cause of the problem
132is that `let mut v0 = &mut v[0]` creates a mutable reference to *part
133of* the object `v`. At this point, `v` is borrowed and cannot be used
134directly anymore, even if we just want to immutably access some other
135parts of it to make a copy. Thus, when we try to do `let x = v[1][0]`,
136the borrow checker complains.
137
138In the first version of my code all of this happens in the same line,
139and this makes it confusing, because the order in which the various
140statements of that line are executed is very important.
141
142## Solution
143
144Solving the problem is easy, I can just use the second version of my code.
145Alternatively I could also try to use `split_at_mut()` as suggested
146by the compiler, but this seems overkill in this case; good to keep in
147mind though.
148
149But sometimes understanding a problem is more important than finding
150a solution.

Generated with cgit - Back to sebastiano.tronto.net