代码之家  ›  专栏  ›  技术社区  ›  Oliv

如何安全地修改可变切片引用字段?

  •  1
  • Oliv  · 技术社区  · 4 年前

    我正在实现一个类似堆栈的结构,其中该结构包含对切片的可变引用。

    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>`
    

    甚至 a simplified version of 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];
       |                     ^^^^^^^^^^^^^^^^^^^^^^^^
    

    有没有一种方法可以实现这一点,或者我需要更明确地跟踪我感兴趣的切片的边界?

    0 回复  |  直到 5 年前
        1
  •  7
  •   trent oli_obk    5 年前

    我稍微修改了Masklinn的代码,允许多个 .pop() s将在同一堆栈上调用:

    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.data 自从 self 是一个可变引用,而不是所有者。因此,编译器试图做的是重新借用 self.data ,用生命的方式“感染”它 &mut self 。这是一条死胡同:我们希望引用为 'a ,但它实际上仅在生命周期内有效 &mut自我 ,这些生命周期通常是不相关的(也不需要相关),这让编译器感到困惑。

    为了帮助编译器,我们使用 std::mem::replace 明确地将切片移出 self.data 并暂时用空切片替换它, which can be any lifetime 现在我们可以做任何事情了 data 而不会与生命纠缠在一起 &mut自我 .

        2
  •  0
  •   Shepmaster Tim Diekmann    5 年前

    对于子问题2,您需要指出以下关系 &mut self 'a 否则,它们被认为是无关的。我不知道是否有通过终身省略的捷径,但如果你指定的话 self 生活为 你没事。

    对于子问题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