如何在不传递给函数的情况下重新借用可变引用?

12

我发现了一个例子,手动内联一个函数会改变借用检查器对该函数的处理方式,导致它无法编译通过。这可能是依赖于函数签名中的信息。如何在内联版本中提供这些信息?

我的想法是怎么工作的

'a'b成为生命周期,'a短于'b(可以写成'b: 'a)。

假设我有一个p:&'b mut f32。我可以暂时借用p(使用&mut p)来获得q:&'a mut &'b mut f32

  1. 我是否正确理解了&'a mut &'b mut f32等同于&'a mut &'a mut f32因为'b: 'a

然后我可以解引用q(使用*q)来获得r:&'a mut f32。我可以通过rf32写入数据(使用*r = something),并稍后(超出生命周期'a)通过p读取该值(使用*p)。

使用函数调用

这里有一些可行的代码,我认为它使用了上述过程:

fn reborrow<'a, 'b: 'a>(q: &'a mut &'b mut f32) -> &'a mut f32 {
    *q
}

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let q = &mut p;
        let r = reborrow(q);
        *r = 2.718;
    }
    assert_eq!(*p, 2.718);
}

reborrow()函数体中,将*q替换为q也能正常工作,因为Rust会在必要时插入缺失的解引用操作。

手动内联

如果我手动内联reborrow()调用,它将无法编译:

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let q = &mut p;
        let r = *q; <-- ERROR REPORTED HERE.
        *r = 2.718;
    }
    assert_eq!(*p, 2.718);
}
error[E0507]: cannot move out of borrowed content
  1. 谁拿走了我的玩具?类型推断在思考/缺失什么?

  2. 我能以某种方式注释let绑定以使编译器推断与先前版本相同的类型吗?

其他尝试

这是另一个可行的版本,但它没有定义名称r

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let q = &mut p;
        **q = 2.718;
    }
    assert_eq!(*p, 2.718);
}

这里有一个解决办法,定义了名字r并且可以工作,但是没有使用相同的借用和解引用顺序:

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let q = &mut p;
        let r = &mut **q;
        *r = 2.718;
    }
    assert_eq!(*p, 2.718);
}

我制作了一个游乐场,将所有四个版本组合在一起。


1
顺便说一句,根据你的“reborrow”方法,我有一个后续问题(http://stackoverflow.com/q/43036307/155423)。 - Shepmaster
1
这可能会有所帮助:https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/ - BurntSushi5
2
@BurntSushi5 我有类似的想法,但是{}技巧在这里不起作用,这似乎表明它在某种程度上是不同的。 - Shepmaster
@BurntSushi5 很有趣,哈哈。 :-) - apt1002
2个回答

14

显而易见的解决方案可行,正如人们所预期的:

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let r: &mut f32 = p;
        *r = 2.718;
    }
    assert_eq!(*p, 2.718);
}

看起来相对直观,这是我认为一个新手最终会得到的结果。


然而,如果你开始思考,它就没有意义了。如描述所示:

  • let r: &mut f32 = p; 移动了p
  • 然而在assert_eq!(*p, 2.718);中我们仍使用了p

一个合理的解释是pCopy,但事实并非如此1

答案是,隐式地,Rust 在幕后执行了一次重新借用。也就是说,显式的代码是:

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let r: &mut f32 = &mut *p;
        *r = 2.718;
    }
    assert_eq!(*p, 2.718);
}
我们可以通过尝试在重新借用后读取p并检查编译器错误来进行检查:
error[E0502]: cannot borrow `p` as immutable because `*p` is also borrowed as mutable
 --> <anon>:6:24
  |
5 |         let r: &mut f32 = p;
  |                           - mutable borrow occurs here
6 |         println!("{}", p);
  |                        ^ immutable borrow occurs here
7 |         *r = 2.718;
8 |     }
  |     - mutable borrow ends here

error: aborting due to previous error
这证实了p确实只是可变地借用,而不是移动、克隆或复制。
1. 可变引用不能是Copy甚至Clone,因为这将违反支撑 Rust 安全性的别名异或可变原则。

好的,我想这至少证实了我在问题中所写的内容。你有一个关于类型推断在这种情况下是如何工作/破坏的模型吗? - apt1002
@apt1002:抱歉,我不确定。我明白发生了什么,但不能确定。如果变量在后面使用,可能只需重新借用,否则移动即可,但可能存在我没有考虑到的特殊情况。 - Matthieu M.

3

我无法详细解释这个问题,但是你可以使用类似于隐式解引用的技巧,将r表示为&mut f32:

fn main() {
    let mut x: f32 = 3.142;
    let mut p = &mut x;
    {
        let q = &mut p;
        let r: &mut f32 = q;
        *r = 2.718;
    }
    assert_eq!(*p, 2.718);
}

1
哇,即使是 let r: &mut f32 = *q; 也能工作。非常奇怪。我真的很期待解释! - apt1002

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