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 的简写,而
    • Self 是 MyIterMut<'a, T>

全部显式写出来是这样的:

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。

  • 你只能重借用一个 &'short 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}");
}

👍

最后修改 August 23, 2026: 更新 (499855b16)