3
votes

I am having issues with a struct that I am passing around as a mutable reference. The issue only occurs when the struct is defined to hold a reference.

struct Global<'a> {
    one: i32,
    two: &'a i32
}

fn do_task<'a, F, R: 'a>(global: &'a mut Global<'a>, task: F)
    where F: Fn(&'a mut Global<'a>) -> &'a R {
    let result = task(global);
}

fn one<'a>(global: &'a mut Global<'a>) -> &'a i32 {
    &global.one
}

fn two<'a>(global: &'a mut Global<'a>) -> &'a i32 {
    global.two
}

fn main() {
    let number = 2;
    let mut global = Global {
        one: 1,
        two: &number
    };
    do_task(&mut global, one);
    do_task(&mut global, two);
}

The borrow checker complains with the following:

error: cannot borrow `global` as mutable more than once at a time
do_task(&mut global, two);
             ^~~~~~
note: previous borrow of `global` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `global` until the borrow ends
do_task(&mut global, one);
             ^~~~~~
note: previous borrow ends here
fn main() {
...
}
^

But, if I change the code so that the field two is not a reference, like in the following example, then it passes.

struct Global {
    one: i32,
    two: i32,
}

fn do_task<'a, F, R: 'a>(global: &'a mut Global, task: F)
    where F: Fn(&'a mut Global) -> &'a R {
    let result = task(global);
}

fn one<'a>(global: &'a mut Global) -> &'a i32 {
    &global.one
}

fn two<'a>(global: &'a mut Global) -> &'a i32 {
    &global.two
}

fn main() {
    let mut global = Global {
        one: 1,
        two: 2
    };
    do_task(&mut global, one);
    do_task(&mut global, two);
}

I tried surrounding the do_task function calls with another scope, but it had no effect.

Why does having the reference extend the mutable borrow to the end of main, and is there any way around this?

I am using rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)

1

1 Answers

6
votes

The problem is that you have accidentally tied together Global’s generic lifetime parameter with the lifetime of the mutable borrow.

When you require &'a mut Global<'a>, that means that the duration of the mutable borrow of the Global must be as long as the reference in two—thus, the full length of the Global’s existence. You have thus made what is inferred to be a permanent borrow of global when you write &mut global. I’ll write it this way in syntax that isn’t quite valid but gets the point across:

fn main() {
    'a: {
        let number: i32 + 'a = 2;
        let mut global: Global<'a> = Global {
            one: 1,
            two: &'a number,
        };
        do_task(&mut global: &'a mut Global<'a>, one);
        do_task(&mut global: &'a mut Global<'a>, two);
    }
}

Each &mut global is borrowing global until the end of the 'a block, and so the second clashes with the first.

You wish to treat those two lifetimes separately. The function should, rather than using a concrete lifetime, bind a lifetime parameter: this is F: for<'b> Fn(&'b mut Global) -> &'b R read it like this: “F should, given an arbitrary lifetime 'b, implement Fn(&'b mut Global) -> &'b R”. The actual functions are then written with the lifetime parameter to Global removed so it can be inferred as another arbitrary lifetime, like so:

fn one<'a>(global: &'a mut Global) -> &'a i32 {
    &global.one
}

Here’s the final result:

struct Global<'a> {
    one: i32,
    two: &'a i32
}

fn do_task<F, R>(global: &mut Global, task: F)
where F: for<'a> Fn(&'a mut Global) -> &'a R {
    let result = task(global);
}

fn one<'a>(global: &'a mut Global) -> &'a i32 {
    &global.one
}

fn two<'a>(global: &'a mut Global) -> &'a i32 {
    global.two
}

fn main() {
    let number = 2;
    let mut global = Global {
        one: 1,
        two: &number
    };
    do_task(&mut global, one);
    do_task(&mut global, two);
}