当借用者范围结束时无法借用变量

10

我不明白为什么可变借用的变量在借用者的作用域结束后仍然被借用。这似乎与特质使用有关,但我不知道原因:

fn main() {
    let mut a = 10;
    test::<FooS>(&mut a);
    println!("out {:?}", a)
}

trait Foo<'a> {
    fn new(data: &'a mut u32) -> Self;
    fn apply(&mut self);
}

struct FooS<'a> {
    data: &'a mut u32,
}

impl<'a> Foo<'a> for FooS<'a> {
    fn new(data: &'a mut u32) -> Self {
        FooS { data: data }
    }

    fn apply(&mut self) {
        *self.data += 10;
    }
}

fn test<'a, F>(data: &'a mut u32)
    where F: Foo<'a>
{
    {
        // let mut foo = FooS {data: data}; // This works fine
        let mut foo: F = Foo::new(data);
        foo.apply();
    } // foo scope ends here
    println!("{:?}", data); // error
} // but borrowed till here

在线尝试

error: cannot borrow `data` as immutable because `*data` is also borrowed as mutable [--explain E0502]
   --> <anon>:34:22
31  |>         let mut foo: F = Foo::new(data);
    |>                                   ---- mutable borrow occurs here
...
34  |>     println!("{:?}", data); // error
    |>                      ^^^^ immutable borrow occurs here
35  |> } // but borrowed till here
    |> - mutable borrow ends here

然而,这似乎是一个错误。 - JDemler
1个回答

8
test函数要求类型 F 实现 Foo<'a>。 这里的 'a 是一个生命周期参数,传递给函数。 生命周期参数始终表示比函数调用更长寿的生命周期,因为调用者无法提供更短寿命的引用; 您如何从另一个函数传递对本地变量的引用?为了借用检查(仅限于函数范围内),编译器认为该借用覆盖整个函数调用。

因此,当您从对 Foo :: new 的调用中创建 F 实例时,您会创建一个借用某些具有生命周期'a的对象,它的寿命涵盖整个函数调用。

重要的是要理解,当您调用 test :: <FooS> 时,编译器实际上为 FooS<'a> 填充生命周期参数,因此您最终调用了 test ::<FooS<'a>>,其中'a 是覆盖包含函数调用的语句的区域(因为&mut a 是临时表达式)。 因此,编译器认为在 test 中构造的 FooS 将借用某些内容,直到具有调用 test 的语句的结尾!

让我们将其与非通用版本对比:

let mut foo = FooS {data: data};

在这个版本中,编译器会为test中的FooS<'a>选择一个具体的生命周期,而不是在main中选择,因此它将选择从let语句结束到块结束的后缀,这意味着下一次对data的借用不会重叠,也不会有冲突。
你真正想要的是,F实现Foo<'x>,其中'x的生命周期比'a更短,并且最重要的是,该生命周期必须是函数内部的区域,而不是像'a那样的封闭区间。
Rust目前解决这个问题的方法是高阶trait限制。它看起来像这样:
fn test<'a, F>(data: &'a mut u32)
    where F: for<'x> Foo<'x>
{
    {
        let mut foo: F = Foo::new(data);
        foo.apply();
    }
    println!("{:?}", data);
}

简单来说,它意味着类型F必须实现每个可能的'xFoo<'x>

虽然这个版本的test可以编译,但我们实际上无法提供满足此限制的类型,因为对于每个可能的生命周期'a,都有一个不同的类型FooS<'a>仅实现Foo<'a>。如果FooS没有生命周期参数并且FooSFoo的实现如下:

impl<'a> Foo<'a> for FooS {

如果存在一个单一类型FooS,该类型实现了每个可能生命周期'aFoo<'a>,那么就没问题了。

当然,您不能在FooS上删除生命周期参数,因为它包含借用指针。解决这个问题的正确方法是Rust尚不具备的特性:能够将类型构造函数(而不是完全构造的类型)作为通用参数传递给函数。有了这种能力,我们就可以使用需要生命周期参数以生成具体类型的类型构造函数FooS调用test,而无需在调用站点指定具体生命周期,调用者将能够提供自己的生命周期。


感谢您的详细回答。我仍然有一个不太清楚的问题。正如您所说的“生命周期参数始终代表比函数调用活得更长的生命周期,并且为了借用检查(仅限于函数内),编译器会考虑这个借用涵盖整个函数调用。”为什么编译器会这样考虑?我们有一个比函数作用域更长的生命周期,但它与涵盖整个函数调用有什么关系?这是实现的细节还是规范的一部分? - Pavel Shander
目前还没有 Rust 的正式“规范”,所以实现是你能得到的最接近规范的东西。关于你的其他问题,请看我的编辑。 - Francis Gagné

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