2.11.1 可变切片迭代器
译文 · 基于 Learning Rust
可变切片迭代器
原文链接: https://quinedot.github.io/rust-learning/lt-ex-mut-slice.html
标准库有一个遍历 &mut [T] 的迭代器,(在撰写本文时)通过指针算术实现,大概是为了优化。在本例中,我们将展示如何用完全安全的代码实现自己的可变切片迭代器。
这是我们实现的起点:
1
2
3
4
5
6
7
8
9
10
11
| struct MyIterMut<'a, T> {
slice: &'a mut [T],
// ……根据你的需要可能还有其他字段……
}
impl<'a, T> Iterator for MyIterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
}
|
下面是几个初步尝试。剧透一下,它们都无法编译。
1
2
3
4
5
6
7
8
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// 嗯,迭代逻辑以后再考虑!
self.slice.get_mut(0)
}
# }
|
1
2
3
4
5
6
7
8
9
10
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// 实际上,这个方法看起来非常适合我们的迭代逻辑
let (first, rest) = self.slice.split_first_mut()?;
self.slice = rest;
Some(first)
}
# }
|
1
2
3
4
5
6
7
8
9
10
11
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// 🤔 模式匹配??
match &mut self.slice {
[] => None,
[first, rest @ ..] => Some(first),
}
}
# }
|
是的,编译器真的不喜欢这些写法。让我们花一分钟把所有省略的生命周期写出来。其中一些在别名中,我们也要展开:
Item 是 &'a mut T&mut self 是 self: &mut Self 的简写,而
全部显式写出来是这样的:
1
2
3
4
5
6
7
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next<'s>(self: &'s mut MyIterMut<'a, T>) -> Option<&'a mut T> {
todo!()
}
# }
|
记住在 MyIterMut<'a, T> 中,slice 是 &'a mut [T]。
啊,是的。我们这里有一个嵌套的独占借用。
你不能通过对 &'short mut &'long mut U 解引用来得到 &'long mut U。
没有安全的方式可以穿过 &'s mut self 并取出 &'a mut T。
那我们被困住了吗?不,实际上有办法前进!事实证明,切片是特殊的。特别是,编译器理解空切片不覆盖任何实际数据,因此不可能有内存别名问题或数据竞争等问题。所以编译器理解,凭空取出一个空切片引用是完全健全的,可以是任意生命周期。即使是独占切片引用!
1
2
3
| fn magic<T>() -> &'static mut [T] {
&mut []
}
|
对我们的目的而言,我们甚至不需要这个魔法:标准库为 &mut [T] 提供了 Default 实现。
这为什么能让我们摆脱困境?有了那个实现,我们可以凭空变出一个空的 &mut [T],并从 &mut self 背后移出我们的 slice 字段:
1
2
3
4
5
6
7
8
9
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
let mut slice = std::mem::take(&mut self.slice);
// 嗯,迭代逻辑以后再考虑!
slice.get_mut(0)
}
# }
|
std::mem::take 以及 swap 和 replace 是非常有用且安全的函数;不要因为它们和危险的 transmute 及其他底层函数一起放在 std::mem 中就被吓倒。注意我们如何传递 &mut self.slice——那是 &mut &mut [T]。take 替换外层 &mut 内部的一切,外层 &mut 可以有任意短的生命周期——只要足够长来移动内存。
所以我们除了迭代逻辑之外就完成了,对吧?这应该永远只给我们第一个元素?
1
2
3
4
5
6
7
8
9
10
11
12
13
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
# fn next(&mut self) -> Option<Self::Item> {
# let mut slice = std::mem::take(&mut self.slice);
# slice.get_mut(0)
# }
# }
let mut arr = [0, 1, 2, 3];
let iter = MyIterMut { slice: &mut arr };
for x in iter.take(10) {
println!("{x}");
}
|
呃,它只给了一个元素。哦对了——当我们用完切片后,需要把它移回 slice 字段。我们只想临时用空切片替换那个字段。
1
2
3
4
5
6
7
8
9
10
11
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
let mut slice = std::mem::take(&mut self.slice);
// 嗯,迭代逻辑以后再考虑!
let first = slice.get_mut(0);
self.slice = slice;
first
}
# }
|
呃哦,现在怎么办。
1
2
3
4
5
6
7
| error[E0499]: cannot borrow `*slice` as mutable more than once at a time
9 | let first = slice.get_mut(0);
| ----- first mutable borrow occurs here
10 | self.slice = slice;
| ^^^^^ second mutable borrow occurs here
11 | first
| ----- returning this value requires that `*slice` is borrowed for `'a`
|
哦,对了!这些是独占引用。我们不能多次返回同一个元素——那意味着如果有人collect了迭代器,他们可能对同一元素获得多个 &mut。想想看,我们也不能推迟迭代逻辑——如果我们试图在分发 &mut T 给元素的同时持有整个 &mut [T],那也是对同一内存的多个 &mut!
这就是错误告诉我们的:我们不能同时持有整个 slice 并返回 first。
(有一种称为"出借迭代器"(lending iterators)的模式,可以以类似迭代器的方式出借你拥有的数据的借用,但当前的 Iterator trait 不支持;这也是另一天的话题。)
好吧,让我们再试试 split_first_mut,那确实看起来非常适合我们的迭代逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
let mut slice = std::mem::take(&mut self.slice);
let (first, rest) = slice.split_first_mut()?;
self.slice = rest;
Some(first)
}
# }
// ……
let mut arr = [0, 1, 2, 3];
let iter = MyIterMut { slice: &mut arr };
for x in iter {
println!("{x}");
}
|
终于,一个可以工作的版本!split_first_mut 是一种拆分借用的形式,我们之前简要提到过。
为了完整起见,这是基于模式匹配的拆分借用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// ....只有这些改变了....
// vvvvvvvvvvvvvvvvvv v
match std::mem::take(&mut self.slice) {
[] => None,
[first, rest @ ..] => Some(first),
}
}
# }
// ……
let mut arr = [0, 1, 2, 3];
let iter = MyIterMut { slice: &mut arr };
for x in iter {
println!("{x}");
}
|
哦糟糕,又只有一个元素。对了。我们需要把 rest 放回 self.slice:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # struct MyIterMut<'a, T> { slice: &'a mut [T], }
# impl<'a, T> Iterator for MyIterMut<'a, T> {
# type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
match std::mem::take(&mut self.slice) {
[] => None,
[first, rest @ ..] => {
self.slice = rest;
Some(first)
}
}
}
# }
// ……
let mut arr = [0, 1, 2, 3];
let iter = MyIterMut { slice: &mut arr };
for x in iter {
println!("{x}");
}
|
👍