【问题标题】:Can I reassign a mutable slice reference to a sub-slice of itself?我可以将可变切片引用重新分配给自身的子切片吗?
【发布时间】:2020-07-28 02:21:41
【问题描述】:

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

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];
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^

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

【问题讨论】:

    标签: rust lifetime borrowing


    【解决方案1】:

    我稍微修改了 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,它用&amp;mut self的生命周期“感染”它。这是一条死胡同:我们希望引用 live for 'a,但它实际上只在 &amp;mut self 的生命周期内有效,而这些生命周期通常是不相关的(并且它们不需要相关),这让编译器感到困惑。

    为了帮助编译器,我们使用std::mem::replace 将切片显式移出self.data,并暂时将其替换为空切片which can be any lifetime。现在我们可以用data 做任何事情,而不用纠结&amp;mut self 的生命周期。

    【讨论】:

    • 太棒了。正是我要建议的。值得注意的是,如果self.data 开始为空(因为当split_last_mut 返回None 时,您丢失了原始切片),切片的“临时”替换最终将是永久性的。实际上,这可能不是问题,因为所有空切片或多或少都是等价的,但如果您对数据的地址做一些棘手的事情,它可能会导致意外。
    • pop 的返回类型不需要持有可变引用,即:pub fn pop(&amp;mut self) -&gt; Option&lt;&amp;'a X&gt;
    • 确实是我自己加的,不然data也可以是&amp;'a [X],问题就简单了。
    【解决方案2】:

    对于子问题 2,您需要指出 &amp;mut self'a 之间的关系,否则它们被视为不相关。我不知道是否存在通过生命周期省略的捷径,但如果您指定 self'a 生存,那么您就可以了。

    对于子问题 1,编译器不会“看穿”函数调用(包括对函数调用去糖的索引),因此它不知道 &amp;self.data[n - 1]&amp;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

    【讨论】:

    • 这样做的一个主要缺点是因为pop 必须采用&amp;'a mut self,您似乎无法调用.pop() 两次,因为它需要一个在整个生命周期内的可变引用的堆栈。 Example
    猜你喜欢
    • 2021-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多