如何说服借用检查员不再使用借用的参考

Tom*_*mas 0 rust

我正在寻找一种方法,如何说服借用检查人员以下代码是安全的。

情况

SliceHolder是一个结构,我无法更改它的接口(不同的库)。我想在 中临时设置切片SliceHolder,调用一些方法来处理数据,然后完成借用(停止借用切片)。我的问题是,借阅检查器不允许我这样做。如果我添加到接口foo where 'a: 'b借用检查器将失败,self.slice = slice;因为输入切片无法存储(即使是临时的)到结构中,寿命将更长(至少我认为这是原因)。如果我更改为where 'b: 'a借用检查器将失败x[0] = 5,因为它认为该切片的借用时间比 SliceHolder 的生命周期长。

我只是想临时使用切片的引用,并在函数foo结束时以某种方式释放该引用,以便借用检查器不会将引用视为已使用。为此,我使用了self.slice = &DEFAULT;应该说服slice不再存储在内部的借用检查器SliceHolder,但这不起作用。

我已经找到了不安全和原始指针(self.slice = unsafe {&*(slice as *const [u8])};)的解决方案,但我认为这不是解决 Rust 中这个问题的正确方法。

代码

struct SliceHolder<'a> {
    slice: &'a [u8],
}

const DEFAULT: [u8; 0] = [];

impl<'a> SliceHolder<'a> {
    fn foo<'b>(&mut self, slice: &'b [u8]) -> Vec<u8> {
        self.slice = slice; // FIRST POSITION WHERE BORROW CHECKER COMPLAINS
        let result = do_something_with_holder(&self);
        self.slice = &DEFAULT;
        result
    }
}

// blackbox function, do some computation based on the slice
fn do_something_with_holder(holder: &SliceHolder) -> Vec<u8> {
    Vec::from(holder.slice)
}


fn main() {
    let mut holder = SliceHolder {
        slice: &DEFAULT,
    };

    let mut x: [u8; 1] = [1];
    holder.foo(&x);
    x[0] = 5;
    holder.foo(&x); // SECOND POSITION WHERE BORROW CHECKER COMPLAINS
}
Run Code Online (Sandbox Code Playgroud)

尝试使用 Rust 1.40。

SCa*_*lla 5

当尝试解决此类生命周期问题时,绘制所有涉及的生命周期图表会很有帮助。有 2 个命名生命周期,但至少有 5 个相关生命周期。这只是为了功能SliceHolder::foo

  • 'static。这是将会有的生命周期&DEFAULT(由于'static提升)。
  • 'a。附加到 类型的生命周期self
  • 'b。切片的生命周期。
  • 匿名生命周期 1(我称之为'c)。&mut借用的生命周期self
  • 匿名一生2(我称之为'd)。与函数体相对应的生命周期。

这是它们关系的图表(请原谅蹩脚的 ASCII 艺术)。图中较高的生命周期通常持续时间较长,但只有当有一系列箭头连接它们时,一个生命周期才会比另一个生命周期长。

        'static
           ^
         /   \
       /       \
     /          \
    |            |
    V            V
   'a           'b
    |            |
    V            |
   'c            |
     \          /
       \      /
         \  /
          V
         'd
Run Code Online (Sandbox Code Playgroud)

添加生命周期'b: 'a界限相当于在该图上添加一个箭头'a -> 'b,表示'aoutlives'b或具有生命周期的借用'a在整个 period 内有效'b

让我们看一下这些生命周期如何在main.

// holder: SliceHolder<'a>, where 'a: 'static
let mut holder = SliceHolder { slice: &DEFAULT };

let mut x: [u8; 1] = [1];

// Take a mutable borrow of holder (with lifetime 'c1)
// Also take a borrow of x with lifetime 'b1
holder.foo(&x);
// mutate x. This forces 'b1 to end no later than here.
x[0] = 5;
// Take another mutable borrow of holder ('c2)
// and another borrow of x ('b2)
holder.foo(&x);
Run Code Online (Sandbox Code Playgroud)

这表明我们不能有'b: 'a,因为这意味着'a必须不晚于'b结束时结束。这将迫使在突变holder之前停止存在。xholder用得晚一些。


所以我们已经证明我们不能拥有'b: 'a. 那还剩下什么?当我们执行赋值时self.slice = slice;,我们隐式地从&'b [u8]to进行转换&'a [u8]。这需要'b: 'a我们刚刚排除的。self总是必须有 type SliceHolder<'a>,所以我们不能仅仅缩短它的生命周期。

旁白:如果我们不坚持 in 中的切片(至少)self 总是'a有生命周期,我们可能会在某个时刻(例如 in do_something_with_holder)出现恐慌,并避免将控制路径self.slice重新分配给具有更长生命周期的东西。'b将结束(invalidating slice: &'b [u8]),但self仍然存在并持有无效引用。如果您使用过代码,这些都是您需要考虑的事情unsafe

然而,我们可以有第二个变量,其生命周期较短,其(即内部切片)与 相同self。我们需要第二个变量的类型的SliceHolder<'_>生命周期不长于'a(以便我们可以使用self的引用)并且不长于'b(以便我们可以分配slice给它的切片)。我们看图可以看到,确实有一个比'a和都短的生命周期'b,即'd

幸运的是,我们不需要担心这一生的命名。重要的是它存在,编译器会解决剩下的问题。那么我们如何获得第二个变量呢?我们需要以某种方式self进入一个新变量。但请记住,我们无法移出可变引用,因此我们必须在内部保留一些有效的内容。

我们已经计划更换self的参考&DEFAULT,那么为什么不这样做呢?相关命令是std::mem::replace并且可以像 一样使用let second_variable = std::mem::replace(self, SliceHolder {slice: &DEFAULT})

有一些稍微更符合人体工程学的方法可以做到这一点,但它需要为SliceHolder. 如果SliceHolder真的只包含一个引用,它可以实现Copy,这使得这种情况下的一切变得更容易。除此之外,实现Default它(作为&DEFAULT默认切片)将允许您使用新稳定的std::mem::take而不是std::mem::replace. 那么您就不需要内联构造默认值。

可能还有其他一些事情需要考虑,但如果没有最小可重复示例,很难说是什么。我会给你一些工作代码(游乐场)

struct SliceHolder<'a> {
    slice: &'a [u8],
}

const DEFAULT: [u8; 0] = [];

struct Result;

fn do_something_with_holder(_: &SliceHolder) -> Result {
    Result
}

impl<'a> SliceHolder<'a> {
    fn foo<'b>(&mut self, slice: &'b [u8]) -> Result {
        // new_holder has the exact value that self would have had before
        let mut new_holder: SliceHolder<'_> =
            std::mem::replace(self, SliceHolder { slice: &DEFAULT });

        new_holder.slice = slice;
        let result = do_something_with_holder(&new_holder);
        // self.slice = &DEFAULT; // no longer needed - we've already taken care of that
        result
    }
}

fn main() {
    let mut holder = SliceHolder { slice: &DEFAULT };

    let mut x: [u8; 1] = [1];
    holder.foo(&x);
    x[0] = 5;
    holder.foo(&x);
}
Run Code Online (Sandbox Code Playgroud)