我可以将可变切片引用重新分配给自身的子切片吗?
Posted
技术标签:
【中文标题】我可以将可变切片引用重新分配给自身的子切片吗?【英文标题】: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];
| ^^^^^^^^^^^^^^^^^^^^^^^^
有没有办法让这个工作,或者我需要更明确地跟踪我感兴趣的切片的边界?
【问题讨论】:
【参考方案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
,它用&mut self
的生命周期“感染”它。这是一条死胡同:我们希望引用 live for 'a
,但它实际上只在 &mut self
的生命周期内有效,而这些生命周期通常是不相关的(并且它们不需要相关),这让编译器感到困惑。
为了帮助编译器,我们使用std::mem::replace
将切片显式移出self.data
,并暂时将其替换为空切片which can be any lifetime。现在我们可以用data
做任何事情,而不用纠结&mut self
的生命周期。
【讨论】:
太棒了。正是我要建议的。值得注意的是,如果self.data
开始为空(因为当split_last_mut
返回None
时,您丢失了原始切片),切片的“临时”替换最终将是永久性的。实际上,这可能不是问题,因为所有空切片或多或少都是等价的,但如果您对数据的地址做一些棘手的事情,它可能会导致意外。
pop
的返回类型不需要持有可变引用,即:pub fn pop(&mut self) -> Option<&'a X>
。
确实是我自己加的,不然data
也可以是&'a [X]
,问题就简单了。【参考方案2】:
对于子问题 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
【讨论】:
这样做的一个主要缺点是因为pop
必须采用&'a mut self
,您似乎无法调用.pop()
两次,因为它需要一个在整个生命周期内的可变引用的堆栈。 Example以上是关于我可以将可变切片引用重新分配给自身的子切片吗?的主要内容,如果未能解决你的问题,请参考以下文章