borrow-checker.md (5289B)
1 # Stunned by the borrow checker 🦀 2 3 As I mentioned in my [last post](../2025-06-13-cargo-culture-shock), 4 in 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) 10 of 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)) 13 and I am now playing around with some past 14 [Advent of Code](https://adventofcode.com/) problems. 15 16 But today I won't talk about any of the above. Instead, I just want to show 17 you a small example of code that kept me confused for a couple of hours. 18 19 First, I need to very briefly explain Rust's concept of ownership. 20 21 ## The borrow checker 22 23 The *borrow checker* is a unique feature of Rust that prevents certain 24 kinds of memory errors and data races. Without going into too much 25 detail, the borrow checker is a compile-time mechanism that ensures that, 26 at any given point, a given object is owned by at most one reference, 27 unless all references to it are *immutable* (that is, they don't allow 28 modifying the object). 29 30 As a simple example, the following code is not valid: 31 32 ``` 33 fn 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 41 And the compiler gives a clear explanation: 42 43 ``` 44 error[E0506]: cannot assign to `x` because it is borrowed 45 --> t.rs:4:5 46 | 47 3 | let y = &x; 48 | -- `x` is borrowed here 49 4 | x = 3; 50 | ^^^^^ `x` is assigned to here but it was already borrowed 51 5 | println!("{x} {y}"); 52 | --- borrow later used here 53 ``` 54 55 What 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 57 used directly anymore until `y` goes out of scope. 58 59 If 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 64 Let's say we have a vector of vectors, and we want to copy an element from 65 one of the internal vectors to another. We could try something like this: 66 67 ``` 68 fn 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 74 But this won't compile. Indeed we get: 75 76 ``` 77 error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable 78 --> a.rs:3:15 79 | 80 3 | 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 89 However, the following works just fine: 90 91 ``` 92 fn main() { 93 let mut v = vec![vec![23], vec![42]]; 94 let x = v[1][0]; 95 v[0].push(x); 96 } 97 ``` 98 99 And at this point I was very confused. Whatever the borrow checker does, 100 shouldn't the two pieces of code do exactly the same? I got stuck for 101 a while thinking that for some reason the function argument of `push()` 102 was passed by reference in the first case, while it was copied in the 103 second, but this is not where the problem lies. 104 105 I was finally able to understand the problem when I realized that my first 106 piece of code is equivalent to the following, which also does not compile: 107 108 ``` 109 fn 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 117 Can you see the issue now? 118 119 ## Explanation 120 121 Like in C++ and many other languages, the square 122 bracket operator is a method on the vector object. 123 More 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) 126 functions, depending if mutability is requested in our usage. In our 127 example, when we call `v[0].push(...)` this will be translated to a call 128 to `index_mut()`, because `push()` requires a mutable reference; when 129 we do e.g. `let x = v[1][0]`, the immutable version will be call instead. 130 131 But the details of `[]` are not important for us. The cause of the problem 132 is that `let mut v0 = &mut v[0]` creates a mutable reference to *part 133 of* the object `v`. At this point, `v` is borrowed and cannot be used 134 directly anymore, even if we just want to immutably access some other 135 parts of it to make a copy. Thus, when we try to do `let x = v[1][0]`, 136 the borrow checker complains. 137 138 In the first version of my code all of this happens in the same line, 139 and this makes it confusing, because the order in which the various 140 statements of that line are executed is very important. 141 142 ## Solution 143 144 Solving the problem is easy, I can just use the second version of my code. 145 Alternatively I could also try to use `split_at_mut()` as suggested 146 by the compiler, but this seems overkill in this case; good to keep in 147 mind though. 148 149 But sometimes understanding a problem is more important than finding 150 a solution.