可变借用到结构体持有引用的函数存在问题

3
我在使用一个可变引用传递的结构体时遇到了问题。当该结构体被定义为持有引用时才会出现此问题。
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);
}

借用检查器报告如下错误:

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() {
...
}
^

但是,如果我更改代码,使得字段two不再是一个引用,就像下面的例子一样,那么它就通过了。

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);
}

我尝试用另一个作用域包围do_task函数调用,但没有效果。

为什么引用会将可变借用延伸到main的末尾,有没有什么方法可以避免这种情况?

我使用的是rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)

1个回答

6
问题在于您意外地将“Global”的通用生命周期参数与可变借用的生命周期绑定在一起。
当您需要“'a mut Global<'a>”时,这意味着对“Global”的可变借用的持续时间必须与“two”中的引用一样长,因此是“Global”存在的整个时间。因此,当您编写“&mut global”时,您已经进行了推断为永久借用的“global”借用。我将以不完全有效但能够表达观点的语法将其写成这样:
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);
    }
}

每个 &mut global 都会借用 global 直到 'a 代码块结束,因此第二个和第一个发生了冲突。
您希望将这两个生命周期分开处理。该函数应该绑定生命周期参数,而不是使用具体生命周期:这是 F: for<'b> Fn(&'b mut Global) -> &'b R,读作:“给定任意生命周期 'bF 应该实现 Fn(&'b mut Global) -> &'b R”。然后,实际函数应该写成去除了 Global 生命周期参数的形式,以便可以推断为另一个任意生命周期,例如:
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);
}

谢谢。你有for<>语法的文档链接吗?我在书籍、参考资料和RFC中都找不到相关内容。 - Dale
@Dale,很有可能这个语法还没有在书中被记录,但是这里有一个RFC介绍了这个语法。它也解释了背后的原理。顺便说一下,它被称为高阶trait约束。 - Vladimir Matveev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接