【问题标题】:Cannot borrow variable when borrower scope ends借用者范围结束时无法借用变量
【发布时间】:2016-08-02 06:31:42
【问题描述】:

我不明白为什么在借用者范围结束后仍会借用可变借用变量。看起来它与特征使用有关,但我不明白为什么:

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

try online

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

【问题讨论】:

标签: rust


【解决方案1】:

test 函数要求类型 F 实现 Foo&lt;'a&gt;'a 有一个传递给函数的生命周期参数。生命周期参数总是代表比函数调用更长的生命周期——因为调用者无法提供生命周期较短的引用;如何从另一个函数传递对局部变量的引用? –,并且为了借用检查(对于函数来说是本地的),编译器认为借用覆盖了整个函数调用。

因此,当您通过对 Foo::new 的调用创建 F 的实例时,您创建的对象会借用生命周期为 'a 的对象,该生命周期涵盖整个函数调用。

重要的是要了解,当您调用test::&lt;FooS&gt; 时,编译器实际上会为FooS&lt;'a&gt; 填充生命周期参数,因此您最终会调用test::&lt;FooS&lt;'a&gt;&gt;,其中'a 是覆盖包含的语句的区域函数调用(因为&amp;mut a 是一个临时表达式)。因此,编译器认为将在test 中构造的FooS 会借用一些东西,直到调用test!

让我们将其与非泛型版本进行对比:

let mut foo = FooS {data: data};

在这个版本中,编译器在test 中为FooS&lt;'a&gt; 选择一个具体的生命周期,而不是在main 中,因此它会选择从let 语句末尾延伸到语句末尾的块后缀该块,这意味着data的下一次借用不重叠,也不存在冲突。

您真正想要的是 F 在比 'a 更短的某个生命周期内实现 Foo&lt;'x&gt; 'x,最重要的是,该生命周期必须是函数内的一个区域,而不是像 @ 这样的封闭区域987654346@是。

Rust 目前对这个问题的解决方案是更高级别的特征界限。它看起来像这样:

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 类型必须为每个可能的'x 实现Foo&lt;'x&gt;

虽然这个版本的test 可以自行编译,但我们实际上无法提供满足此约束的类型,因为对于每个可能的生命周期'a,都有一个独特的类型FooS&lt;'a&gt; 只实现Foo&lt;'a&gt;。如果FooS 没有生命周期参数并且FooFooS 的实现看起来像这样:

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

那就没问题了,因为有一个单一类型 FooS 在每个可能的生命周期 'a 中实现 Foo&lt;'a&gt;

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

【讨论】:

  • 感谢您的详细解答。对我来说还有一个不清楚的时刻。正如您所说的 “生命周期参数始终表示比函数调用寿命更长的生命周期,并且出于借用检查的目的(这是函数的本地),编译器认为借用涵盖了整个函数调用。”。为什么编译器编译器会考虑这个?我们的生命周期比函数范围更长。但它与借用的关系如何涵盖整个函数调用?是实现细节还是规范的一部分?
  • Rust 还没有合适的“规范”,所以实现是最接近你将获得的规范的东西。对于您的其他问题,请参阅我的编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-12
  • 2022-11-26
  • 2017-02-11
  • 1970-01-01
  • 2015-04-20
  • 2015-10-16
相关资源
最近更新 更多