Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Who borrowed a variable?

I'm fighting with the borrow checker. I have two similar pieces of code, one working as I expect, and the other not.

The one that works as I expect:

mod case1 {
    struct Foo {}

    struct Bar1 {
        x: Foo,
    }

    impl Bar1 {
        fn f<'a>(&'a mut self) -> &'a Foo {
            &self.x
        }
    }

    // only for example
    fn f1() {
        let mut bar = Bar1 { x: Foo {} };
        let y = bar.f(); // (1) 'bar' is borrowed by 'y'
        let z = bar.f();  // error (as expected) : cannot borrow `bar` as mutable more
                           // than once at a time [E0499]
    }

    fn f2() {
        let mut bar = Bar1 { x: Foo {} };
        bar.f(); // (2) 'bar' is not borrowed after the call
        let z = bar.f();  // ok (as expected)
    }
}

The one that doesn't:

mod case2 {
    struct Foo {}

    struct Bar2<'b> {
        x: &'b Foo,
    }

    impl<'b> Bar2<'b> {
        fn f(&'b mut self) -> &'b Foo {
            self.x
        }
    }

    fn f4() {
        let foo = Foo {};
        let mut bar2 = Bar2 { x: &foo };
        bar2.f(); // (3) 'bar2' is borrowed as mutable, but who borrowed it?
        let z = bar2.f(); // error: cannot borrow `bar2` as mutable more than once at a time [E0499]
    }
}

I hoped I could call Bar2::f twice without irritating the compiler, as in case 1.

The question is in the comment (3): who borrowed bar2, whereas there is no affectation?

Here's what I understand:

  1. In case 1, f2 call: the lifetime parameter 'a is the one of the receiving &Foo value, so this lifetime is empty when there is no affectation, and bar is not borrowed after the Bar1::f call;

  2. In case 2, bar2 borrows foo (as immutable), so the lifetime parameter 'b in Bar2 struct is the foo reference lifetime, which ends at the end of f4 body. Calling Bar2::f borrows bar2 for that lifetime, namely to the end of f4.

But the question is still: who borrowed bar2? Could it be Bar2::f? How Bar2::f would hold the borrowed ownership after the call? What am I missing here?

I'm using Rust 1.14.0-nightly (86affcdf6 2016-09-28) on x86_64-pc-windows-msvc.

like image 417
jferard Avatar asked Oct 03 '16 08:10

jferard


People also ask

What is borrowing in Rust?

What is Borrowing? When a function transfers its control over a variable/value to another function temporarily, for a while, it is called borrowing. This is achieved by passing a reference to the variable (& var_name) rather than passing the variable/value itself to the function.

What is a borrow checker?

Borrowing is accessing the value of a variable without taking ownership of the variable by referencing the owner. The borrow checker ensures that the reference is valid, and the data isn't dropped using a construct called lifetimes. A lifetime is how long a variable exists.

How do you pass a mutable reference in Rust?

Mutable References First, we change s to be mut . Then we create a mutable reference with &mut s where we call the change function, and update the function signature to accept a mutable reference with some_string: &mut String . This makes it very clear that the change function will mutate the value it borrows.

What are references in Rust?

A reference represents a borrow of some owned value. You can get one by using the & or &mut operators on a value, or by using a ref or ref mut pattern.


2 Answers

Ah... you basically self-borrowed yourself.

The issue hinges on the fact that you have the same lifetime ('b) used for both the lifetime of Foo and the lifetime of Bar. The compiler then dutifully unifies those lifetimes, and you end up in a strange situation where suddenly the lifetime of the borrow which should have ended at the end of the statement instead ends after the value should have gone out of scope.

As a rule of thumb: always use a fresh lifetime for self. Anything else is weird.


It's interesting to note that this pattern can actually be useful (though more likely with an immutable borrow): it allows anchoring a value to a stack frame, preventing any move after the call to the function, which is (sometimes) useful to represent a borrow that is not well-modeled by Rust (like passing a pointer to the value to FFI).

like image 196
Matthieu M. Avatar answered Sep 23 '22 05:09

Matthieu M.


In case #2, you have this:

impl<'b> Bar2<'b> {
    fn f(&'b mut self) -> &'b Foo {
        self.x
    }
}

To highlight: &'b mut self and &'b Foo have the same lifetime specified.

This is saying that the reference to self and the returned reference to an instance of a Foo both have the same lifetime. Looking at the call site, you have this:

let foo = Foo {};
let mut bar2 = Bar2 { x: &foo };

So the compiler is inferring that both foo and bar2 have the same lifetime. The lifetime of foo is the scope of the f4 function, and so the mutable reference to bar2 shares this.

One way to fix this, is to remove the explicit lifetime on the self reference:

fn f(&mut self) -> &'b Foo

This compiles and the compiler correctly understands that the reference to bar2 and the reference to foo have different lifetimes.

Playground: https://play.rust-lang.org/?gist=caf262dd628cf14cc2884a3af842276a&version=stable&backtrace=0

TLDR: Yes, having the same lifetime specifier on the self reference and the returned reference means that the entire scope of f4 holds a mutable borrow of bar2.

like image 22
Simon Whitehead Avatar answered Sep 22 '22 05:09

Simon Whitehead