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