Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused about borrowing error in struct method

Tags:

rust

I'm trying to write some code for an entity system in a game but I'm getting this error so I've boiled the code into something that does the same thing, I get the same error in the actual code.

I don't understand why the compiler is telling me that the reference to self.my_list goes out of scope when the function baz finishes.

My rust version is rustc 1.3.0 (9a92aaf19 2015-09-15)

I would have thought that it goes out of scope when the for loop ends?

struct Foo {
    name : &'static str,
}

struct Bar {
    my_list : Vec<Foo>,
}

impl Bar {
    fn New() -> Bar {
        let mut new_instance = Bar { my_list : vec!() };
        new_instance.my_list.push(Foo { name : "foo1" });
        new_instance.my_list.push(Foo { name : "foo2" });
        new_instance.my_list.push(Foo { name : "foo3" });
        return new_instance;
    }

    fn Baz(&mut self, name : &'static str) -> Option<&Foo> {
        for x in &self.my_list {
            if x.name == name {
                return Some(x);
            }
        }

        self.my_list.push(Foo { name : "foo" });

        return None;
    }
}

fn main() {
    let mut bar = Bar::New();
    if let Some(x) = bar.Baz("foo1") {
        println!("{} found", x.name);
    }
}

This is the error message that I get:

Compiling tutorial v0.1.0 (file:///C:/Code/Projects/rust/tutorial)
src\main.rs:35:9: 35:21 error: cannot borrow `self.my_list` as mutable because it is also borrowed as immutable
src\main.rs:35         self.my_list.push(Foo { name : "foo" });
                       ^~~~~~~~~~~~
src\main.rs:29:19: 29:31 note: previous borrow of `self.my_list` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `self.my_list` until the borrow ends
src\main.rs:29         for x in &self.my_list {
                                 ^~~~~~~~~~~~
note: in expansion of for loop expansion
src\main.rs:29:9: 33:10 note: expansion site
src\main.rs:38:6: 38:6 note: previous borrow ends here
src\main.rs:28     fn Baz(&mut self, name : &'static str) -> Option<&Foo> {
...
src\main.rs:38     }
                   ^
error: aborting due to previous error
Could not compile `tutorial`.

To learn more, run the command again with --verbose.
like image 995
Tom Tetlaw Avatar asked Oct 01 '15 07:10

Tom Tetlaw


1 Answers

This is a limitation of the borrow checker. The way you're reasoning that the function is safe is essentially treating the function as two paths: along the path where the function returns Some(x), you don't borrow my_list again, and along the path where the function returns None, the borrow ends after the for loop.

That isn't the way the borrow checker looks at the function; the borrow checker works in terms of lexical scopes. It tries to pick a scope as narrow as possible, but it will treat the the whole function as the scope if necessary. It sees that the lifetime in the return value is the same as the lifetime of self, therefore the lifetime of the value referenced by x must be the same as self, therefore the borrow &self.my_list must have the same lifetime as self, therefore the borrow lasts beyond the return of the function.

If you write the function differently, Rust will accept it:

fn Baz(&mut self, name : &'static str) -> Option<&Foo> {
    match self.my_list.iter().position(|x| x.name == name) {
        Some(i) => Some(&self.my_list[i]),
        None => {
            self.my_list.push(Foo { name : "foo" });
            None
        }
    }
}
like image 105
Eli Friedman Avatar answered Sep 30 '22 09:09

Eli Friedman