Yes, you can

on Tom van Dijk's blog

turn off the borrow checker.

This article is meant to prove something to my friends about the borrow checker.

The problem: is it believed the borrow checker is unnessecary and is constantly being fought against.

Today, I will use the rust crate you_can to demonstrate why this is not true.

An example of why the borrow checker is fought against:

1fn main() {
2    let a = vec!["hello,", "world!"];
3    let b = a;
4    println!("{:?}", a);
5}

This will cause a compiler error:

This is for a good number of reasons:

  1. What happens to a if we made b mutable and mutated it?
  2. Should a be passed into b by value or by reference?
  3. Should a be cloned into b, in order to have 2 instances instead?

Rust solves this by making everything explicit. If we did let b = &a; we explicitly told the compiler to keep a reference of a. By the way, if T is printable, then &T is, too. If we wanted to have the data in 2 places, without any connection between them, we could clone it:

1let b = a.clone();

In the first case though, rust passes the ownership of a into b. This makes a invalid until redefined. This is the heart of the rust borrow checker.

The rust borrow checker defines simple ownership rules to decide how memory should be managed, without a garbage collector.

  1. Each value in Rust has an owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

From the rust book, chapter 4.1

Some other rule is the borrowing rule: You can have either multiple immutable references, or a singular mutable reference at a time.

Thus, the following is not possible:

1let mut a = "some string";
2let b = &mut a;
3let c = &mut a;

This is for a good reason: when doing this concurrently, you will end up with data races (also known as Undefined Behaviour or UB). For this reason Mutexes exist.

If you struggle with the ownership problem in rust, I highly recommend you thoroughly (re)reading the chapter.

What I wanted to show you today is, you don't have to fight against the borrow checker. Just add to your Cargo.toml:

1[dependencies]
2you-can = "0.0.14"

Next, change your code. This time, we produce 2 mutable references to 1 value, then use them after free to demonstrate the consequences.

 1// main.rs
 2#[you_can::turn_off_the_borrow_checker]
 3fn main() {
 4    let mut a = vec!["hello,", "world!"];
 5    let b = &mut a[0];
 6    let c = &mut a[1];
 7    drop(a);
 8    // UB
 9    println!("{}{}", b, c);
10}

This will not produce any compiler errors, but the you_can crate does warn us (on nightly):

you_can warning us about unsafe behavior and the code segfaulting

And yes, this produces UB. In this case, it just segfaults. But there's no unsafe or raw pointers, right? Well, this is why #[you_should::leave_the_borrow_checker_on]. Memory safety is lost when suppressing the borrow checker. And no, in the end, when you have learned Rust's memory model, you are not "fighting" the borrow checker. It's a helping hand, telling you everything that is wrong with your code regarding memory safety.

This will enable the programmer to write fast, safe, reliable code without a garbage collector or manual allocations.

Further reading/watching (in no particular order)