我能否将可变切片的引用重新分配给其自身的子切片?

6
我正在实现一个类似于栈的结构,其中该结构持有对切片的可变引用。
struct StackLike<'a, X> {
    data: &'a mut [X],
}

我希望能够弹出该堆栈的最后一个元素,类似于以下方式:
impl<'a, X> StackLike<'a, X> {
    pub fn pop(&mut self) -> Option<&'a X> {
        if self.data.is_empty() {
            return None;
        }
        let n = self.data.len();
        let result = &self.data[n - 1];
        self.data = &mut self.data[0..n - 1];
        Some(result)
    }
}

这个失败了:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/lib.rs:11:23
   |
11 |         let result = &self.data[n - 1];
   |                       ^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 6:5...
  --> src/lib.rs:6:5
   |
6  | /     pub fn pop(&mut self) -> Option<&'a X> {
7  | |         if self.data.is_empty() {
8  | |             return None;
9  | |         }
...  |
13 | |         Some(result)
14 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:11:23
   |
11 |         let result = &self.data[n - 1];
   |                       ^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 5:6...
  --> src/lib.rs:5:6
   |
5  | impl<'a, X> StackLike<'a, X> {
   |      ^^
note: ...so that the expression is assignable
  --> src/lib.rs:13:9
   |
13 |         Some(result)
   |         ^^^^^^^^^^^^
   = note: expected  `std::option::Option<&'a X>`
              found  `std::option::Option<&X>`

即使是返回值简化、仅缩小切片的pop的简化版本也无效。

impl<'a, X> StackLike<'a, X> {
    pub fn pop_no_return(&mut self) {
        if self.data.is_empty() {
            return;
        }
        let n = self.data.len();
        self.data = &mut self.data[0..n - 1];
    }
}

这提供了

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/lib.rs:11:26
   |
11 |         self.data = &mut self.data[0..n - 1];
   |                          ^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 6:5...
  --> src/lib.rs:6:5
   |
6  | /     pub fn pop_no_return(&mut self) {
7  | |         if self.data.is_empty() {
8  | |             return;
9  | |         }
10 | |         let n = self.data.len();
11 | |         self.data = &mut self.data[0..n - 1];
12 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:11:26
   |
11 |         self.data = &mut self.data[0..n - 1];
   |                          ^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 5:6...
  --> src/lib.rs:5:6
   |
5  | impl<'a, X> StackLike<'a, X> {
   |      ^^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:11:21
   |
11 |         self.data = &mut self.data[0..n - 1];
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^

有没有办法让这个工作起来,或者我需要更明确地跟踪我感兴趣的切片的范围?

2个回答

7

我稍微修改了Masklinn的代码,使得同一个堆栈上可以调用多个.pop()

struct StackLike<'a, X> {
    data: &'a mut [X],
}

impl<'a, X> StackLike<'a, X> {
    pub fn pop(&mut self) -> Option<&'a mut X> {
        let data = std::mem::replace(&mut self.data, &mut []);
        if let Some((last, subslice)) = data.split_last_mut() {
            self.data = subslice;
            Some(last)
        } else {
            None
        }
    }
}

fn main() {
    let mut data = [1, 2, 3, 4, 5];
    let mut stack = StackLike { data: &mut data };

    let x = stack.pop().unwrap();
    let y = stack.pop().unwrap();
    println!("X: {}, Y: {}", x, y);
}

这里的难点是这一行代码(我添加了类型注释以明确表述):

let data: &'a mut [X] = std::mem::replace(&mut self.data, &mut []);

我们临时用一个空切片替换self.data,以便我们可以分割该切片。如果您只写
let data: &'a mut [X] = self.data;

编译器会不高兴:
error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/main.rs:7:33
   |
7  |         let data: &'a mut [X] = self.data;
   |                                 ^^^^^^^^^
   |
note: ...the reference is valid for the lifetime `'a` as defined on the impl at 5:6...
  --> src/main.rs:5:6
   |
5  | impl<'a,  X> StackLike<'a, X> {
   |      ^^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 6:5
  --> src/main.rs:6:5
   |
6  | /     pub fn pop(&mut self) -> Option<&'a mut X> {
7  | |         let data: &'a mut [X] = self.data;
8  | |         if let Some((last, subslice)) = data.split_last_mut() {
9  | |             self.data = subslice;
...  |
13 | |         }
14 | |     }
   | |_____^

据我所理解,问题在于self.data是一个可变引用,而可变引用不是Copy(请记住,您一次只能拥有一个引用)。由于self是一个可变引用而不是所有者,因此您无法将其移出self.data。因此编译器尝试重新借用self.data,这会将其“感染”&mut self的生命周期。这是死路一条:我们希望该引用存在于'a的生命周期中,但它实际上仅在&mut self的生命周期内有效,并且这些生命周期通常是无关的(且它们不需要相关),这使得编译器感到困惑。
为了帮助编译器,我们使用std::mem::replace显式地将切片从self.data中移出,并暂时替换为空切片可以是任何生命周期。现在,我们可以对data进行任何操作,而不会涉及到&mut self的生命周期。

1
太棒了,正是我想建议的。值得注意的是,如果 self.data 初始为空,则“临时”替换的切片最终将变成永久性的(因为当split_last_mut返回None时,你会失去原始切片)。实际上,这可能不是问题,因为所有空切片都多多少少是等价的,但如果你在处理数据地址时做了一些棘手的事情,它可能会导致意外。 - trent
1
pop 的返回类型不需要持有可变引用,即:pub fn pop(&mut self) -> Option<&'a X> - Peter Hall
的确,我自己添加了它,否则 data 可以是 &'a [X],这个问题就会变得更容易一些。 - kreo

0
对于子问题2,你需要指出&mut self'a之间的关系,否则它们被视为不相关。我不知道是否有通过生命周期省略的快捷方式,但如果你指定self的生存期为'a,就可以了。
对于子问题1,编译器无法“穿透”函数调用(包括索引,它被展开为函数调用),因此它不知道&self.data[n - 1]&mut self.data[0..n-1]是非重叠的。你需要使用split_mut_last
struct StackLike<'a, X> {
    data: &'a mut [X],
}

impl<'a, X> StackLike<'a, X> {
    pub fn pop(&'a mut self) -> Option<&'a X> {
        if let Some((last, subslice)) = self.data.split_last_mut() {
            self.data = subslice;
            Some(last)
        } else {
            None
        }
    }
}

playground


2
这个的一个相当大的缺点是,因为pop必须采用&'a mut self,所以似乎你无法调用.pop()两次,因为它需要具有整个堆栈生命周期的可变引用。示例 - Frxstrem

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