2.6 IterMut
原文链接: https://rust-unofficial.github.io/too-many-lists/second-iter-mut.html
说实话,IterMut 很狂野。这话本身听起来就很狂野;从语义上看,它和 Iter 一模一样吧!
语义上是的,但共享引用与可变引用的性质意味着 Iter 是「轻而易举」的,而 IterMut 则是正经的巫师魔法。
关键洞察来自我们对 Iter 的 Iterator 实现:
1
2
3
4
5
| impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> { /* stuff */ }
}
|
可以脱糖为:
1
2
3
4
5
| impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next<'b>(&'b mut self) -> Option<&'a T> { /* stuff */ }
}
|
next 的签名在输入与输出的生命周期之间建立了零约束!为什么在意?意味着可以无条件地反复调用 next!
1
2
3
4
5
6
7
| let mut list = List::new();
list.push(1); list.push(2); list.push(3);
let mut iter = list.iter();
let x = iter.next().unwrap();
let y = iter.next().unwrap();
let z = iter.next().unwrap();
|
酷!
对共享引用这绝对没问题,因为要点就是可以同时有很多个。可变引用则不能共存,要点就是独占。
结果是,用安全代码写 IterMut 明显更难(我们还没讲那意味着什么……)。令人惊讶的是,对很多结构 IterMut 其实可以完全安全地实现!
先把 Iter 的代码拿过来,全部改成可变:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| pub struct IterMut<'a, T> {
next: Option<&'a mut Node<T>>,
}
impl<T> List<T> {
pub fn iter_mut(&self) -> IterMut<'_, T> {
IterMut { next: self.head.as_deref_mut() }
}
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node| {
self.next = node.next.as_deref_mut();
&mut node.elem
})
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| > cargo build
error[E0596]: cannot borrow `self.head` as mutable, as it is behind a `&` reference
--> src/second.rs:95:25
|
94 | pub fn iter_mut(&self) -> IterMut<'_, T> {
| ----- help: consider changing this to be a mutable reference: `&mut self`
95 | IterMut { next: self.head.as_deref_mut() }
| ^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
error[E0507]: cannot move out of borrowed content
--> src/second.rs:103:9
|
103 | self.next.map(|node| {
| ^^^^^^^^^ cannot move out of borrowed content
|
两个不同错误。第一个很清楚,还告诉我们怎么修:不能把共享引用升级为可变引用,所以 iter_mut 需要 &mut self。只是愚蠢的复制粘贴错误。
1
2
3
| pub fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut { next: self.head.as_deref_mut() }
}
|
另一个呢?
糟糕!写上一节 iter 实现时我其实犯了个错,只是碰巧能跑!
我们第一次撞上 Copy 的魔法。讲所有权时说过,移动之后就不能再用了。对某些类型这完全合理。老朋友 Box 帮我们管理堆上的分配,我们当然不希望两段代码都觉得自己该释放那块内存。
但对其他类型这纯属垃圾。整数没有所有权语义,只是无意义的数字!所以整数标为 Copy。Copy 类型可以通过按位复制完美复制。因此它们有超能力:移动之后旧值仍然可用。于是甚至可以从引用里移出 Copy 类型而无需替换!
Rust 里所有数值原语(i32、u64、bool、f32、char 等)都是 Copy。只要所有组成部分都是 Copy,用户自定义类型也可以声明为 Copy。
关键的是这段代码能跑的原因:共享引用也是 Copy!因为 & 是 Copy,Option<&> 也是 Copy。所以 self.next.map 没问题,Option 只是被复制了。现在不行了,因为 &mut 不是 Copy(复制 &mut 会有两个 &mut 指向同一内存,禁止)。应该正确地 take 这个 Option。
1
2
3
4
5
6
| fn next(&mut self) -> Option<Self::Item> {
self.next.take().map(|node| {
self.next = node.next.as_deref_mut();
&mut node.elem
})
}
|
呃……哇。IterMut 直接就能用了!
测试一下:
1
2
3
4
5
6
7
8
9
10
| #[test]
fn iter_mut() {
let mut list = List::new();
list.push(1); list.push(2); list.push(3);
let mut iter = list.iter_mut();
assert_eq!(iter.next(), Some(&mut 3));
assert_eq!(iter.next(), Some(&mut 2));
assert_eq!(iter.next(), Some(&mut 1));
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| > cargo test
Running target/debug/lists-5c71138492ad4b4a
running 6 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::iter_mut ... ok
test second::test::into_iter ... ok
test second::test::iter ... ok
test second::test::peek ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured
|
没错。能用。
天哪。
什么。
好吧,它本来就该能工作,但通常总有什么愚蠢的东西挡路!说清楚:
我们刚实现了一段代码,接收单链表,最多一次返回链表中每个元素的可变引用。而且静态验证保证如此。完全安全。我们也没做什么疯狂的事。
要我说,这挺了不起的。能工作有几个原因:
- 我们对
Option<&mut> 做 take,独占可变引用,不用担心有人再看一眼。 - Rust 理解可以把可变引用拆成所指结构体的子字段,因为没法「往上走回去」,而且它们肯定不相交。
同样的基本逻辑也能为数组或树得到安全的 IterMut!甚至可以做 DoubleEnded 迭代器,从前和后同时消费!哇!