IterMut
老实说,IterMut很狂野。 说起来似乎很荒唐。 当然,它与Iter相同!
从语义上讲,是的。但是共享和可变引用的性质意味着 Iter 是“琐碎的” ,而 IterMut 是合法的向导魔法。
关键洞察力来自于我们对 Iter 的 Iterator 实现:
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> { /* stuff */ }
}
可以将其语法糖去除:
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next<'b>(&'b mut self) -> Option<&'a T> { /* stuff */ }
}
next 的签名在输入和输出的生存期之间没有任何约束! 我们为什么在乎? 这意味着我们可以无条件地反复调用 next!
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 代码开始,然后把所有东西都改成可变的:
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_mut().map(|node| &mut **node) }
}
}
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_mut().map(|node| &mut **node);
&mut node.elem
})
}
}
> 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_mut().map(|node| &mut **node) }
| ^^^^^^^^^ `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。 只是一个愚蠢的复制粘贴错误。
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut { next: self.head.as_mut().map(|node| &mut **node) }
}
那另一个呢?
哎呀!实际上,我在前面的部分中写 iter 的时候不小心犯了一个错误,而且我们很幸运它能正常工作!
我们刚刚第一次尝试了复制的魔力。当我们引入所有权时,我们说过当你移动东西的时候,你就不能再使用它了。对于某些类型来说,这是完全合理的。我们的好朋友 Box 为我们管理堆上的一个分配,我们当然不希望释放这两段代码的内存。
但是对于其他类型来说,这就是垃圾。整数没有所有权语义,它们只是毫无意义的数字!这就是为什么将整数标记为 Copy 的原因。众所周知,按位复制是完全可复制的。因此,它们有一种超能力: 当移动时,旧值仍然可用。因此,您甚至可以将 Copy 类型从引用中移出,而无需替换!
Rust中的所有数值原语(i32、u64、bool、f32、char等)都是Copy。只要其所有字段都声明为 Copy 类型,也可以将任何用户自定义类型声明为 Copy。
至关重要的是,为什么这段代码起作用了,共享的引用也被复制了! 因为&是副本,所以Option<&> 也是Copy。 因此,当我们执行self.next.map 时,Option 已被复制。 现在,我们无法执行此操作,因为&mut 不是复制(如果您复制了 &mut,则将两个 &mut 移到内存中的同一位置,这是禁止的)。 相反,我们应该适当地使用 Option 来获取它。
fn next(&mut self) -> Option<Self::Item> {
self.next.take().map(|node| {
self.next = node.next.as_mut().map(|node| &mut **node);
&mut node.elem
})
}
> cargo build
呃...哇 天哪! IterMut正常工作!
让我们来测试一下:
#[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));
}
> 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
It works.
天啊。
什么?
我的意思是它实际上应该是有效的,但是通常会有一些愚蠢的东西阻碍它!我们先说清楚:
我们刚刚实现了一段代码,它接受一个单链链表,最多一次返回对列表中每个元素的可变引用。这是静态验证的。而且非常安全。我们不必做任何疯狂的事。
如果你问我的话,那是件大事。这有两个原因可以解释:
我们采用 Option<&mut> ,这样我们就可以独占地访问可变的引用。不用担心有其他人再次使用。
Rust 知道将一个可变的引用切分到指向结构体的子字段中是可以的,因为没有办法“返回” ,而且它们肯定是不相交的。
事实证明,您可以应用这个基本逻辑为数组或树获取一个安全的IterMut!您甚至可以将迭代器设为双端,这样您就可以同时从前面和后面使用迭代器!哇哦!
Last updated
Was this helpful?