为什么这个借用仍然“活跃”?

时间:2021-04-18 09:38:56

标签: rust

这是我遇到的事情的简化示例:

trait Bla<'a> {
    fn create(a: &'a Foo) -> Self;
    fn consume(self) -> u8;
}

struct Foo;
impl Foo {
    fn take(&mut self, u: u8) {}
}

struct Bar {
    foo: Foo,
}

impl Bar {
    fn foobar<'a, 'b, B: Bla<'b>>(&'a mut self)
    where 'a: 'b {
        let u = {
            // immutable borrow occurs here
            // type annotation requires that `self.foo` is borrowed for `'b`
            let foo: &'b Foo = &self.foo;
            let b = B::create(foo);
            b.consume()
        };

        // error[E0502]: cannot borrow `self.foo` as mutable because it is also borrowed as immutable
        self.foo.take(u);
    }
}

为什么 self.foo 即使在退出它被借用的块之后仍然被认为是被借用的,使用借用的所有东西也被删除了?

2 个答案:

答案 0 :(得分:10)

数据可能流动到另一个地方。
要理解为什么编译器认为不可变借用 可能 仍然存在是正确的,请考虑以下有效的 Bla 实现:

#![feature(once_cell)]

// The data stored in this global static variable exists for the entirety of runtime.
static REFS: SyncLazy<Mutex<Vec<&'static Foo>>> = SyncLazy::new(|| Mutex::new(vec![]));

struct Bad;
impl Bla<'static> for Bad {
    fn create(a: &'static Foo) -> Self {
        // The reference can go somewhere other than `Self`!
        REFS.lock().unwrap().push(a);
        Bad
    }

    fn consume(self) -> u8 {
        // Even if `self` is consumed here, 
        // `self` doesn't necessarily destory the immutable borrow.
        0
    }
}

当您的通用 foobar 函数接受 Bla 的任何有效实现时,有效的实现可能会使用 &'static 借用,以保证借用持续整个运行时。因此,它一个错误,因为 consume 不能保证不可变借用消失。


您可能正在寻找 for<> 语法,因为它允许 Bad 实现存在但不允许任何人使用 foobar 调用 Bad

impl Bar {
    fn foobar<B: for<'b> Bla<'b>>(&mut self) {
        let u = {
                let foo: &Foo = &self.foo;
                let b = B::create(foo);
                b.consume()
        };
        self.foo.take(u);
    }
}
fn main() {
    Bar::foobar::<Bad>(&mut Bar { foo: Foo })
    // ^ error: implementation of `Bla` is not general enough
    // | `Bad` must implement `Bla<'0>`, for any lifetime `'0`...
    // | ...but it actually implements `Bla<'static>`
}

如果不是这种情况,您可能需要提供更多实现细节,而不是导致诊断错误的示例。

答案 1 :(得分:6)

通常,当您不指定自己的生命周期时,变量的不可变借用的生命周期会在变量超出范围时结束。但是在您的代码中,您指定了不可变借用的显式生命周期 'b

对于借用检查器,唯一知道的关于生命周期 'b 是它是 'a 的子集(由于 where 'a: 'b)。特别是,借用检查器不会假设不可变借用的范围在 } 结束,因为没有指定关于 'b 的那种类型。

编译器实际上是大声说出来的:

<块引用>

// 类型注解要求 self.foo 被借用为 'b

总结

<块引用>

为什么即使在退出它被借用的块之后,self.foo 仍然被认为是被借用的,使用借用的所有东西也被删除了?

因为您指定的生命周期不会在块结束时结束。借用检查器不关心与借用相关的一些变量超出范围。

相关问题