sebastiano.tronto.net

Source files and build scripts for my personal website
git clone https://git.tronto.net/sebastiano.tronto.net
Download | Log | Files | Refs | README

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.