Comprehensive Rust 实战用 Collatz 序列练习掌握 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本篇文章来自 Google Android 团队维护的 Rust 课程 Comprehensive Rust当前仓库即该课程的完整教学源码聚焦其「控制流基础」章节的实战练习——Collatz 序列。该练习要求你用刚学到的while/loop/for循环、if表达式与函数定义实现一个计算 Collatz 序列长度的函数并附带可运行的完整测试。读完本文你不仅能独立完成这道练习还能吃透 Rust 中mut参数、表达式化if、隐式返回、todo!()宏等核心语言特性并学会用 Cargo 与 Bazel 两种方式构建、运行和测试你的解答。练习背景什么是 Collatz 序列Collatz 猜想又称 3n1 问题是数论中一个著名且至今未被证明的猜想。对任意大于零的整数n₁按如下规则递推生成序列若nᵢ 1则序列终止于nᵢ若nᵢ为偶数则nᵢ₊₁nᵢ/ 2若nᵢ为奇数则nᵢ₊₁ 3 ×nᵢ 1。猜想声称无论从哪个正整数出发序列最终都会到达 1。虽然尚未被证明但对所有在计算机可表示范围内的数该猜想均已被验证成立因此它非常适合作为初学者的编程练习。以n₁ 3 为例原文档给出了完整的推导过程3 是奇数所以n₂ 3 × 3 1 1010 是偶数所以n₃ 10 / 2 55 是奇数所以n₄ 3 × 5 1 1616 是偶数所以n₅ 16 / 2 88 是偶数所以n₆ 8 / 2 44 是偶数所以n₇ 4 / 2 22 是偶数所以n₈ 2 / 2 1序列终止。从 3 出发的序列共包含 8 个数3, 10, 5, 16, 8, 4, 2, 1。这个例子是验证你实现是否正确的最佳手算样例。练习要求与代码骨架课程要求你编写一个函数计算给定初始值n的 Collatz 序列长度。所谓长度就是从n出发、一直递推到 1 为止所包含的项数包含n本身和最终的 1。原文档给出了如下带占位符的代码骨架fn collatz_length(mut n: i32) - u32 { todo!(Implement this) } fn main() { // 课程正文中此处会 include exercise.rs 中带 main 标注的片段 // 即println!(Length: {}, collatz_length(11)); }这段骨架在课程源码中通过 mdbook 的{{#include exercise.rs:collatz_length}}与{{#include exercise.rs:main}}两个锚点机制从 exercise.rs 里提取真实代码片段注入到讲义页面中对应锚点为源码中的// ANCHOR: collatz_length与// ANCHOR: main注释确保讲义与可运行源码永远保持同步。骨架已经为你确定了函数签名这是解题的核心约束参数mut n: i32——n是i32类型且被声明为可变的mut返回值u32——序列长度是正整数用无符号 32 位整数表达。todo!()宏的作用占位符todo!(Implement this)来自 Rust 标准库宏其行为在课程的 Macros 一节中有明确说明todo!()标记一段尚未实现的代码一旦被执行就会 panic。这意味着它能让代码先通过编译占位符类型可满足任何返回类型要求但运行时一旦走到该分支就会崩溃从而提醒开发者功能尚未完成讲义中这段代码使用了should_panic属性标注正是为了向学生演示带着todo!()运行程序会触发 panic。在真实的开发流程中todo!()是渐进式开发先搭骨架、后填实现的常用工具与unreachable!()标记不应到达的分支、assert!系列断言一起构成标准库宏家族。解题思路拆解在动手写代码前先把问题分解为可操作的三步初始化计数器序列长度至少为 1因为n本身就是第一项。循环递推只要当前值大于 1就按奇偶性应用对应规则得到下一个值同时长度加 1。终止返回当当前值变为 1 时循环结束返回累计长度。关键观察奇偶判断可以用n % 2 0完成规则本质上是一条「按条件选择下一步取值」的逻辑这正好对应 Rust 中if表达式的用法——Rust 的if是一个产生值的表达式可以直接把分支结果赋值给变量。完整解答源码级逐行解析课程的官方解答保存在 solution.md 与 exercise.rs 中完整实现如下/// Determine the length of the collatz sequence beginning at n. fn collatz_length(mut n: i32) - u32 { let mut len 1; while n 1 { n if n % 2 0 { n / 2 } else { 3 * n 1 }; len 1; } len } fn main() { println!(Length: {}, collatz_length(11)); // should be 15 }这段不到十行的代码浓缩了该章节全部核心语法原文档 solution.md 明确归纳了三个关键特性1.mut参数函数内可变的局部绑定参数mut n: i32使形参n在函数作用域内成为可变局部变量。要注意的是这完全不影响调用方的值——因为i32是实现了Copy的类型参数按值传递时发生拷贝函数内无论怎么修改n调用者手中的变量都原封不动。这正是 Rust 所有权与Copy语义的一个直观体现更深入的内容见课程的 Memory Management 章节。2.if表达式分支结果直接赋值n if n % 2 0 { n / 2 } else { 3 * n 1 };Rust 的if与许多语言不同它是表达式而非语句每个分支块的最后一条表达式即为整个if的值相关语法细节见 if expressions。因此这里可以把if/else的结果直接赋给n比在分支内部各自写n ...再else的方式更简洁。随之而来的硬性约束是两个分支必须产生相同类型。如果像let size if x 20 { small } else { large };这样把if用在let语句中语句末尾还必须有分号——这两个细节都是课程强调的常见编译错误来源在small后多加;或去掉let语句末尾的;编译器都会立刻报错。3. 隐式返回省略分号的尾表达式函数体以len结尾且不带分号这个「裸值」形式的尾表达式自动成为函数的返回值等价于显式return len;。这正是 functions.md 中强调的惯用法return关键字保留给提前退出使用函数末尾的普通返回值应使用裸表达式形式。同时课程也指出如果函数没有返回值应返回单元类型()编译器会在省略返回类型时自动推断。循环结构的选择解答使用while n 1这是三种循环里最贴合本问题的循环次数事先未知且退出条件明确n降到 1。课程的 Loops 章节系统对比了三种循环while条件为真时持续执行循环体与其他语言用法一致适合「先判断再迭代」的场景loop无条件无限循环直到遇到break。课程 loop 一节特别指出loop是唯一能返回非平凡值的循环构造——因为它的退出点必然是break语句而break可以携带一个表达式作为loop表达式的值见 break 和 continuefor对区间或集合元素迭代底层依赖迭代器iterator机制见 for本练习中若已知最大步数也可以配合break使用但while最直观。若想练习loop也可以这样改写解答fn collatz_length(mut n: i32) - u32 { let mut len 1; loop { if n 1 { break len; } n if n % 2 0 { n / 2 } else { 3 * n 1 }; len 1; } }这里break len将序列长度作为loop表达式的值返回恰好展示了loop独有的「break 携带返回值」特性。边界情况与陷阱写出健壮的实现原文档 solution.md 的折叠备注中明确指出了两个值得深究的边界问题n ≤ 0时的行为Collatz 序列的定义前提是n 严格大于 0。但函数签名却接受i32这意味着调用者可能传入0或负数此时0是偶数递推会变成0 → 0 → 0 …while n 1条件下的循环会永不终止死循环负数同样可能陷入无限循环或产生非预期行为。课程指出更健壮的实现可以改用u32参数从类型层面杜绝非法输入或返回Option/Result显式处理非法值。在课程早期的练习阶段接受i32并依赖调用约定是教学上的简化但你应该意识到在真实项目中用类型系统编码前置条件如用u32比在运行时检查更符合 Rust 的工程哲学——这与课程「Idiomatic Rust」部分所倡导的「利用类型系统」思想一脉相承。整数溢出风险当n为奇数时执行3 * n 1在i32范围内较大的n可能让中间结果溢出。这一点与原文档提到的 Fibonacci 练习中的溢出风险如出一辙。在 debug 构建下 Rust 会对算术溢出触发 panicrelease 构建下则会回绕wrap around产生错误结果。虽然对 32 位范围内的起始值而言 Collatz 序列通常不会溢出的结论已被广泛验证但严谨的实现仍应意识到这一理论风险。运行与测试Cargo 和 Bazel 双轨验证这个练习不是「纸上谈兵」——仓库为它配备了完整的构建与测试配置你可以立刻在本地验证解答。用 Cargo 运行src/control-flow-basics/Cargo.toml 将该练习配置为独立的 Cargo 包edition 2024并声明了二进制目标[[bin]] name collatz path exercise.rs在仓库根目录执行cargo run --bin collatz -p control-flow-basics正常输出应为Length: 15——因为从 11 出发的 Collatz 序列11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1恰好有 15 项这也是 exercise.rs 中main函数与注释// should be 15的由来。单元测试与 Bazelexercise.rs 还内置了一个单元测试#[test] fn test_collatz_length() { assert_eq!(collatz_length(11), 15); }配合 src/control-flow-basics/BUILD.bazel 中的 Bazel 规则rust_binary构建collatz可执行文件rust_test复用同一 crate 生成collatz_test测试目标可以用两种方式运行测试# Cargo 方式在仓库根目录 cargo test -p control-flow-basics # Bazel 方式在仓库根目录 bazel test //src/control-flow-basics:collatz_test该测试验证了练习的核心验收标准collatz_length(11) 15。完成实现后你可以用3期望长度 8、1期望长度 1即序列只有一项等手算样例补充更多断言进一步巩固对算法和测试写法的理解。本练习在课程中的位置与延伸阅读本练习是 Comprehensive Rust 课程「Control Flow Basics」部分的收官实战位于 控制流基础目录 之下紧接在以下讲义的讲解之后是对它们的综合运用Functions函数签名、返回类型、尾表达式与隐式返回if expressionsif作为表达式使用分支类型必须一致Loops 及 loop、for三种循环的选择break 和 continueloop中break携带返回值的用法match 表达式作为if/else链的替代方案——例如你可以用match n % 2 { 0 n / 2, _ 3 * n 1 }改写奇偶分支体会模式匹配的简洁注意match同样要求穷尽性与分支类型一致Macrostodo!()、println!、dbg!等标准库宏的用途。完成本练习后你便掌握了 Rust 中函数、表达式、循环与宏的组合运用能力这正是后续所有权、借用、模式匹配等进阶章节的语法基础。建议在本地用cargo run亲手跑通一遍再尝试用loop、match或u32参数写出自己的变体比较不同写法在简洁性与健壮性上的取舍。【免费下载链接】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),仅供参考