
Rust 数组迭代实战for 循环、IntoIterator 与断言宏——comprehensive-rust 课程精讲【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust数组Array是 Rust 中最基础、最重要的复合类型之一而对数组的迭代则是编写日常逻辑时绕不开的操作。本文以 Google Android 团队维护的开源 Rust 课程 comprehensive-rust 中 数组迭代一节 为骨架结合同目录下的 数组基础、元组 以及 for 循环、IntoIterator 等章节系统讲解 Rust 中for语句迭代数组的语法、背后的 trait 机制、所有权语义以及assert_ne!等断言宏的用法。读完本文你将能够熟练使用for遍历数组与多维数组、理解元组不能迭代的原因、掌握断言宏在数组元素校验中的实战写法并为后续学习迭代器打下基础。for语句支持迭代数组最小可用示例在 Rust 中for语句可以直接迭代数组array但不能迭代元组tuple。这是数组迭代最核心的一条语法事实也是本节教学的出发点。// Copyright 2023 Google LLC // SPDX-License-Identifier: Apache-2.0 fn main() { let primes [2, 3, 5, 7, 11, 13, 17, 19]; for prime in primes { for i in 2..prime { assert_ne!(prime % i, 0); } } }这段课程示例见 iteration.md做了两件事外层for迭代数组for prime in primes依次取出数组中的每一个素数内层for迭代范围for i in 2..prime使用半开区间2..prime包含 2不包含prime逐一尝试除数断言校验assert_ne!(prime % i, 0)断言prime不能被i整除用于验证数组中的每个数确实都是素数。如果把这段代码放在 rust playground 或本课程的 mdbook 环境含editable标记读者可直接在线运行中执行程序不会产生任何输出也不会 panic——因为数组中每一个素数除以2..prime范围内任意整数的余数都必然非 0所有断言全部通过。这正是断言宏在测试与程序内联校验中的典型用法用程序自身验证程序的不变量。数组的类型本质为什么for能迭代数组要理解数组迭代先要理解数组本身。在 arrays.md 中课程明确指出数组类型的形态为[T; N]T是元素类型N是编译期常量的长度长度是类型的一部分[u8; 3]与[u8; 4]是两种不同的类型数组不是堆分配的它是固定大小、编译期已知尺寸的普通值存放在栈上这一点与默认堆分配数组的 GC 语言不同数组的长度在编译期固定运行时无法添加或删除元素。正因如此数组的可迭代性有着非常明确的静态基础所有元素的类型相同同为T、数量确定N编译器可以安全地为for生成遍历逻辑。课程 for 循环一节 展示了更一般的情形——for既可以遍历范围也可以遍历集合字面量fn main() { for x in 1..5 { dbg!(x); } for elem in [2, 4, 8, 16, 32] { dbg!(elem); } }注意第一个循环只迭代到 4因为1..5是半开区间若想包含 5应使用闭区间语法1..5。而第二个循环直接对一个数组字面量[2, 4, 8, 16, 32]迭代说明for对数组的支持是开箱即用的。为什么元组不能迭代类型异构的限制for不支持迭代元组这一点看似限制实则由类型系统决定。在 tuples.md 中课程说明了原因元组把不同类型的值组合成一个复合类型例如(i8, bool)for循环要求被迭代的所有元素具有相同类型而元组的各字段类型可能各不相同因此无法进行统一迭代与数组一样元组的长度和字段类型同样在编译期固定运行时无法增删元素。换句话说能否被for迭代这一能力与类型同质性直接绑定数组是同质元素homogeneous的定长集合元组是异质元素heterogeneous的定长集合。同质才可统一遍历异质只能通过.0、.1等下标注访问字段。底层机制for循环如何工作课程 iteration.md 特别提示数组迭代的功能依赖IntoIteratortrait只是此时尚未讲到该 trait。要理解for的真正工作原理需要把视线投向 IntoIterator 与 Iterator 两章。for语句本质上是对迭代器模式的语法糖IntoIterator定义如何为一个类型创建迭代器for循环会自动调用它Iterator定义创建迭代器之后如何逐个取值核心方法是next返回OptionItem。课程 intoiterator.md 中的示例定义了一个自定义类型Grid并为其实现IntoIteratorstruct Grid { x_coords: Vecu32, y_coords: Vecu32, } impl IntoIterator for Grid { type Item (u32, u32); type IntoIter GridIter; fn into_iter(self) - GridIter { GridIter { grid: self, i: 0, j: 0 } } } struct GridIter { grid: Grid, i: usize, j: usize, } impl Iterator for GridIter { type Item (u32, u32); fn next(mut self) - Option(u32, u32) { if self.i self.grid.x_coords.len() { self.i 0; self.j 1; if self.j self.grid.y_coords.len() { return None; } } let res Some((self.grid.x_coords[self.i], self.grid.y_coords[self.j])); self.i 1; res } } fn main() { let grid Grid { x_coords: vec![3, 5, 7, 9], y_coords: vec![10, 20, 30, 40] }; for (x, y) in grid { println!(point {x}, {y}); } }这个例子揭示了三条关键知识IntoIterator的实现必须声明两个关联类型Item被迭代的元素类型与IntoIter由into_iter返回的迭代器类型且迭代器返回的必须是与Item对应的OptionItem迭代器是惰性的创建迭代器只是初始化状态不执行任何实际工作只有调用next才产生值见 iterator.md 中的SliceIter示例迭代器不一定是有限的半开范围0..可以一直产生值直到整数溢出。对于数组而言标准库已经为[T; N]实现了IntoIterator这正是for prime in primes无需任何额外代码即可工作的原因。VecT、VecT、[T]以及各类范围也都实现了IntoIterator——这也是for i in some_vec可以工作、但some_vec.next()却不存在的原因for走的是IntoIterator而Vec本身并不实现Iterator。所有权语义按值迭代与按引用迭代数组迭代还有一个容易被忽视的语义细节IntoIterator::into_iter会获取self的所有权。课程 intoiterator.md 建议读者尝试在main中连续迭代grid两次结果必然编译失败——因为第一次for已经通过into_iter把grid移动走了。标准库类型同样如此for e in some_vector会获取some_vector的所有权迭代出的是拥有的元素for e in some_vector则迭代出对元素的引用原集合仍然可用。这一语义同样适用于数组数组元素若为Copy类型如i32for prime in primes实际上以复制的方式逐个取出了值因此迭代结束后primes数组本身仍然可用数组是可复制、可移动的普通值见 arrays.md。若元素类型不实现Copy按值迭代就会移动元素、消耗数组。这一所有权层面的差别是 Rust 与许多 GC 语言在迭代行为上最显著的差异之一也是后续学习借用与所有权时必须时刻留意的点。断言宏assert!、assert_eq!、assert_ne! 及其调试变体数组迭代示例中首次出现了assert_ne!宏课程借此引出了 Rust 断言宏家族宏作用通过条件assert!(expr)断言表达式为真expr为trueassert_eq!(a, b)断言两个值相等a bassert_ne!(a, b)断言两个值不相等a ! b这三类断言宏在任何构建build中都会执行检查无论 debug 还是 release 构建都不例外。与之相对的调试变体debug_assert!以及debug_assert_eq!、debug_assert_ne!只在 debug 构建中生效在 release 构建中会被编译为空操作compile to nothing不产生任何运行时开销。assert_ne!(prime % i, 0)的语义是如果prime % i的结果为 0说明i整除prime宏就会 panic 并输出两个操作数的值帮助定位是哪一对素数/除数组合违反了条件。在数组迭代的上下文中断言宏是边遍历边校验的利器——例如在课程 数组嵌套练习 及其 参考解答 中练习代码以assert_eq!校验矩阵转置结果的正确性// ANCHOR: transpose节选自 src/tuples-and-arrays/exercise.rs fn transpose(matrix: [[i32; 3]; 3]) - [[i32; 3]; 3] { let mut result [[0; 3]; 3]; for i in 0..3 { for j in 0..3 { result[j][i] matrix[i][j]; } } result } #[test] fn test_transpose() { let matrix [ [101, 102, 103], [201, 202, 203], [301, 302, 303], ]; let transposed transpose(matrix); assert_eq!( transposed, [ [101, 201, 301], [102, 202, 302], [103, 203, 303], ] ); }这个练习把数组迭代推向多维场景[[i32; 3]; 3]是3 个[i32; 3]数组组成的数组即典型的二维矩阵表示for row in matrix依次迭代每一行而assert_eq!则验证转置后行列互换的正确性测试代码见 exercise.rs该文件中的main还演示了如何用for row in matrix配合println!({row:?})逐行打印矩阵。从源码结构还可以看到练习刻意在转置实现中使用基于0..3范围的索引迭代solution.md 中说明这样更直观并把更强大的迭代器留到后续章节再展开。实战小结数组迭代的正确打开方式把本文涉及的知识点串联起来可以归纳出数组迭代的完整心智模型语法for x in array即可遍历数组遍历范围时注意1..5半开与1..5闭的区别见 for 循环能力边界for支持数组、Vec、切片、范围等一切实现了IntoIterator的类型但不支持元组因为元组元素类型可能异构底层机制for是IntoIterator::into_iterIterator::next的语法糖迭代器惰性求值Item与IntoIter是关联类型见 IntoIterator所有权按值迭代会移动或复制对Copy类型而言元素需要保留集合时用for x in collection按引用迭代校验用assert!/assert_eq!/assert_ne!在迭代中验证不变量追求性能且仅在调试期校验时使用debug_*变体扩展多维数组嵌套数组同样可以用多层for迭代这是后续编写矩阵、网格类算法如课程中的矩阵转置练习的基础。掌握了以上六点你就拥有了在 Rust 中安全、高效地遍历数组的全部前置知识。接下来不妨前往 数组迭代练习 亲手实现一个矩阵转置并在 mdbook 环境中在线运行 数组迭代示例验证你对for循环、数组类型与断言宏的理解。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考