双可变借用中的生命周期不匹配

Lifetime mismatch in a double mutable borrow

我正在尝试修改可变值的借用,这是一个最小的示例:

fn main() {
    let mut w: [char; 5] = ['h', 'e', 'l', 'l', 'o'];
    let mut wslice: &mut [char] = &mut w;
    advance_slice(&mut wslice);
    advance_slice(&mut wslice);
}

fn advance_slice(s: &mut &mut [char]) {
    let new: &mut [char] = &mut s[1..];
    *s = new;
}

编译器给我这个错误:

error[E0623]: lifetime mismatch
  --> src/main.rs:10:10
   |
8  | fn advance_slice(s: &mut &mut [char]) {
   |                     ----------------
   |                     |
   |                     these two types are declared with different lifetimes...
9  |     let new: &mut [char] = &mut s[1..];
10 |     *s = new;
   |          ^^^ ...but data from `s` flows into `s` here

我试过给两个 borrow 相同的生命周期,但没有成功。 如果我删除 w.

的可变性,这也有效

这个错误消息确实很不幸,我认为它没有很好地解释这里发生的事情。这个问题有点牵强。

错误是 advance_slice() 函数的局部错误,因此我们需要查看的就是这些。切片项的类型是无关紧要的,所以让我们采用这个函数定义:

fn advance_slice_mut<T>(s: &mut &mut [T]) {
    let new = &mut s[1..];
    *s = new;
}

第一行在原始切片的第一项之后创建一个新的切片对象。

为什么允许这样做?难道我们现在没有 两个 对相同数据的可变引用吗?原始切片 *s 包含新切片 new,并且都允许修改数据。这是合法的原因是 *s 在创建子切片时被 隐式重新借用 ,并且 *s 在借用的生命周期内不能再次使用,所以我们仍然只有一个对子切片中数据的活动引用。 reborrow 的作用域是函数 advance_slice_mut(),因此它的生命周期比原始切片短,这是你得到错误的根本原因——你实际上是在尝试分配一个只存在到结束的切片函数到比函数调用寿命更长的内存位置。

每次调用通过可变引用获取参数的函数时,都会发生这种隐式重新借用,包括在 &mut s[1..] 中对 index_mut() 的隐式调用。不能复制可变引用,因为这会创建对同一内存的两个可变引用,并且 Rust 语言设计者决定隐式重新借用是比默认移动可变引用更符合人体工程学的解决方案。不过,shared 引用不会发生重借,因为它们可以自由复制。这意味着 &s[1..] 将具有与原始作用域相同的生命周期,因为两个重叠的不可变切片共存是完全没问题的。这解释了为什么您的函数定义适用于不可变切片。

那么我们该如何解决这个问题呢?我相信你打算做的是绝对安全的——在将新切片重新分配给旧切片后,旧切片就消失了,所以我们没有两个对同一内存的并发可变引用。要在安全代码中创建与原始切片具有相同生命周期的切片,我们需要将原始切片移出我们获得的引用。我们可以通过用空切片替换它来做到这一点:

pub fn advance_slice_mut<T>(s: &mut &mut [T]) {
    let slice = std::mem::replace(s, &mut []);
    *s = &mut slice[1..];
}

或者,我们也可以求助于不安全代码:

use std::slice::from_raw_parts_mut;

pub fn advance_slice_mut<T>(s: &mut &mut [T]) {
    unsafe {
        assert!(!s.is_empty());
        *s = from_raw_parts_mut(s.as_mut_ptr().add(1), s.len() - 1);
    }
}