当前位置: 首页 > news >正文

100 Exercises To Learn Rust 挑战!if・Panic・演练

之前的文章

【0】准备
【1】构文・整数・变量 ← 上回
【2】 if・Panic・演练 ← 本次

这是“100 Exercise To Learn Rust”的第2次练习!本次的主题包括 if 表达式、panic 机制,以及对前面内容的总结练习。

本次相关的页面如下:

  • 2.3. Branching: if/else
  • 2.4. Panics
  • 2.5. Factorial

[02_basic_calculator/03_if_else] if式

  • 2.3. Branching: if/else

问题如下:

/// Return `true` if `n` is even, `false` otherwise.
fn is_even(n: u32) -> bool {todo!()
}#[cfg(test)]
mod tests {use crate::is_even;#[test]fn one() {assert!(!is_even(1));}#[test]fn two() {assert!(is_even(2));}#[test]fn high() {assert!(!is_even(231));}
}

要求返回 n 是偶数时为 true,n 是奇数时为 false。

解释

如果忘记这是在用 Rust,而是直接使用 if 来编写的话,可能会像这样写吧?

fn is_even(n: u32) -> bool {let result;if n % 2 == 0 {result = true;} else {result = false;}return result;
}

不过,这段源码实在让人忍不住想吐槽很多地方。正好借此机会,我们可以尝试使用一个叫 Clippy 的 linter 工具。

cargo clippy
    Checking if_else v0.1.0 (/path/to/100-exercises-to-learn-rust/exercises/02_basic_calculator/03_if_else)
warning: function `is_even` is never used--> exercises/02_basic_calculator/03_if_else/src/lib.rs:2:4|
2 | fn is_even(n: u32) -> bool {|    ^^^^^^^|= note: `#[warn(dead_code)]` on by defaultwarning: unneeded `return` statement--> exercises/02_basic_calculator/03_if_else/src/lib.rs:11:5|
11 |     return result;|     ^^^^^^^^^^^^^|= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return= note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`|
11 -     return result;
11 +     result|warning: unneeded late initialization--> exercises/02_basic_calculator/03_if_else/src/lib.rs:3:5|
3 |     let result;|     ^^^^^^^^^^^|= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init= note: `#[warn(clippy::needless_late_init)]` on by default
help: declare `result` here|
5 |     let result = if n % 2 == 0 {|     ++++++++++++
help: remove the assignments from the branches|
6 ~         true
7 |     } else {
8 ~         false|
help: add a semicolon after the `if` expression|
9 |     };|      +warning: this if-then-else expression assigns a bool literal--> exercises/02_basic_calculator/03_if_else/src/lib.rs:5:5|
5 | /     if n % 2 == 0 {
6 | |         result = true;
7 | |     } else {
8 | |         result = false;
9 | |     }| |_____^ help: you can reduce it to: `result = n % 2 == 0;`|= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign= note: `#[warn(clippy::needless_bool_assign)]` on by defaultwarning: `if_else` (lib) generated 4 warnings (run `cargo clippy --fix --lib -p if_else` to apply 2 suggestions)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s

第一个警告:function 'is_even' is never used 可以忽略。

关于 unneeded 'return' statement: 由于 Rust 是一种面向表达式的语言,函数最后的 return 是不必要的。

关于 unneeded late initialization: 同样地,由于 Rust 是面向表达式的语言,if 结构也是一个表达式(在练习中有解释),因此不需要在各个分支中对 result 进行初始化。

综上所述(忽略最后一个警告),代码可以改成这样!

fn is_even(n: u32) -> bool {if n % 2 == 0 {true} else {false}
}

由于 if 是一个表达式,因此可以将返回值直接绑定到 result 上(冗长的写法已折叠)。
但其实我们可以注意到,函数会返回最后被计算的值,因此根本不需要绑定到 result
总结来说,上述代码中,函数的返回值将是 if 表达式的计算结果,从而正确返回了 n 的偶数或奇数判定!

$ cargo clippyChecking if_else v0.1.0 (/path/to/100-exercises-to-learn-rust/exercises/02_basic_calculator/03_if_else)
warning: function `is_even` is never used--> exercises/02_basic_calculator/03_if_else/src/lib.rs:2:4|
2 | fn is_even(n: u32) -> bool {|    ^^^^^^^|= note: `#[warn(dead_code)]` on by defaultwarning: this if-then-else expression returns a bool literal--> exercises/02_basic_calculator/03_if_else/src/lib.rs:3:5|
3 | /     if n % 2 == 0 {
4 | |         true
5 | |     } else {
6 | |         false
7 | |     }| |_____^ help: you can reduce it to: `n % 2 == 0`|= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool= note: `#[warn(clippy::needless_bool)]` on by defaultwarning: `if_else` (lib) generated 2 warnings (run `cargo clippy --fix --lib -p if_else` to apply 1 suggestion)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
fn is_even(n: u32) -> bool {n % 2 == 0
}

其实根本不需要 if 表达式啊...!我以为是 if 表达式的问题,所以陷入了先入为主的思维...

顺便说一下,模范解答里也没有用 if 表达式。这是故意设计成这样的题目吗?

在这种时候,Clippy 真是太伟大了...!

[02_basic_calculator/04_panics] Panic

  • 2.4. Panics

这是问题的内容。虽然有点长,但因为包含了解题所需的信息,所以连测试部分也一起提供了。

/// Given the start and end points of a journey, and the time it took to complete the journey,
/// calculate the average speed of the journey.
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {// TODO: Panic with a custom message if `time_elapsed` is 0(end - start) / time_elapsed
}#[cfg(test)]
mod tests {use crate::speed;#[test]fn case1() {assert_eq!(speed(0, 10, 10), 1);}#[test]// 👇 With the `#[should_panic]` annotation we can assert that we expect the code//    under test to panic. We can also check the panic message by using `expected`.//    This is all part of Rust's built-in test framework!#[should_panic(expected = "The journey took no time at all, that's impossible!")]fn by_zero() {speed(0, 10, 0);}
}

首先,前提是 speed 函数在出现除以 0 的情况时会发生 panic。这本身没有问题,但在这种情况下,希望输出测试中指定的自定义消息:The journey took no time at all, that's impossible!

Rust Playground

Rust Playground

Exercises 中介绍了这个工具,所以我也在这里介绍一下。Rust Playground 是一个类似 Wandbox、ideone、TS playground的在线编辑器,但专门用于 Rust。

和 Wandbox 一样,它可以通过永久链接分享源代码,因此在不创建项目时快速检查语法,或者在社交媒体上讨论 Rust 时都非常方便。一定要试试哦!

解释

如果 time_elapsed == 0,我们可以预先触发 panic,并输出想要显示的消息。这可以通过 if 表达式和 panic! 宏来实现。(这个问题才是更适合用 if 的场景吧?)

fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {// TODO: Panic with a custom message if `time_elapsed` is 0if time_elapsed == 0 {panic!("The journey took no time at all, that's impossible!");}(end - start) / time_elapsed
}

这段话可以翻译为:


顺便提一下,在这种情况下,使用 assert_* 系列宏会很方便!因为我们想确认 time_elapsed != 0,所以这次可以使用assert_ne宏。

fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {// TODO: Panic with a custom message if `time_elapsed` is 0assert_ne!(time_elapsed, 0,"The journey took no time at all, that's impossible!");(end - start) / time_elapsed
}

这段话可以翻译为:


assert_* 系列宏可以理解为“保证条件被满足”,这样理解起来会更容易。

  • assert_eq: 保证第一个参数和第二个参数相等,如果不相等就会触发 panic。
  • assert_ne: 保证第一个参数和第二个参数不相等,如果相等就会触发 panic。

这些宏如果不熟悉,确实容易混淆。此外,第三个参数可以指定 panic 时的自定义消息,因此可以在这里传入测试中要求的文本。

虽然在行数上使用 ifassert_* 宏区别不大(甚至可能多一行),但因为能传达“保证”的含义,所以可能有一部分人会觉得这种写法更易读。

问:听说使用 Result 类型更好是吗?

答:是的。在这种可能会出现错误的情况下,与其直接引发 panic,不如使用程序可以自定义错误处理的 Result 类型更为理想。(据说在 “100 Exercises” 中期会介绍这个概念。)

虽然在不熟悉的情况下尽可能使用 Result 类型确实不会出错,但理想情况下,我们也应该学习如何在合适的场景下使用 panic。以下是一些适合使用 panic(也就是 unwrap 系方法)的情况。

  • 在编译时已确定值且不可能发生 panic 的情况下
    使用 unwrap反而会让代码更易读。

  • main 函数的开头读取环境变量或依赖文件时
    使用 Result 进行分支处理往往显得冗长。在这种情况下,使用 expect会是个不错的选择。

一般来说,Result 类型适用于处理用户输入等“在运行时未确定输入”的情况。考虑到大多数程序都涉及这样的场景(尽管这有些偏见),所以使用 Result 类型的情况会更多。

[02_basic_calculator/05_factorial] 尝试编写函数

  • 2.5. Factorial

问题是...除了测试部分之外,只有注释,所以我将注释内容总结如下:

请定义一个接收非负整数 n 的函数 factorial,并返回 n 的阶乘(n!)。
阶乘是从 1 到 n 的所有正整数的乘积。例如:5! = 5 * 4 * 3 * 2 * 1 = 120
0! 定义为 1。
factorial(0) 应返回 1,factorial(1) 也应返回 1,factorial(2) 应返回 2...依此类推。
请使用到目前为止所学的内容。你还没有学到循环,所以请使用递归来实现!

问题:

// Define a function named `factorial` that, given a non-negative integer `n`,
// returns `n!`, the factorial of `n`.
//
// The factorial of `n` is defined as the product of all positive integers up to `n`.
// For example, `5!` (read "five factorial") is `5 * 4 * 3 * 2 * 1`, which is `120`.
// `0!` is defined to be `1`.
//
// We expect `factorial(0)` to return `1`, `factorial(1)` to return `1`,
// `factorial(2)` to return `2`, and so on.
//
// Use only what you learned! No loops yet, so you'll have to use recursion!#[cfg(test)]
mod tests {use crate::factorial;#[test]fn first() {assert_eq!(factorial(0), 1);}#[test]fn second() {assert_eq!(factorial(1), 1);}#[test]fn third() {assert_eq!(factorial(2), 2);}#[test]fn fifth() {assert_eq!(factorial(5), 120);}
}

确实,我们在 Rust 里还没学到循环,但就这么直接用递归真的好吗……算了,没关系。

解释

只需要写一个用于终止递归的保护条件,以及一个用于递归求值的部分就行了。

fn factorial(n: u32) -> u32 {if n == 0 {return 1;}n * factorial(n - 1)
}

这道题作为到目前为止问题的总结是很合适的。

  • 语法
    通过定义函数的形式复习了之前的内容。

  • 非负整数
    将类型设为 u32 后,就不必担心传入负数作为参数,因此不需要额外的异常处理。

  • 变量
    以函数参数的形式出现。

  • if 表达式和 Panic
    如果传入 0 作为参数,执行 n - 1 会引发 panic,但因为有保护条件阻止了这种情况的发生,所以没有问题。
    虽然在我的写法中没有使用表达式,但模范解答似乎要求对表达式有一定的理解。

回顾这道题,发现它设计得相当好,让人惊讶。

总结

虽然还没涉及到循环等更高级的语法,但最后这个问题确实相当有趣。

那么,让我们进入下一个问题吧!

下一篇文章: 【3】可变性・循环・溢出

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 从零开始构建霸王餐返利APP的技术路线与挑战
  • “前缀和”专题篇二
  • “程序员的艺术转身:AI绘画副业,从代码到画布的变现之旅“
  • 【文件IO】文件内容操作
  • jmeter使用while控制器时防止死循环
  • 临床数据科学和金融数据科学,选择R语言吧!
  • Python操作MongoDB文档存储
  • workerman下的webman路由浏览器跨域的一种问题
  • Docker详解
  • sh脚本发送邮件到多个收件人如何高效实现?
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • 前端面试题整理-Javascript
  • 凤凰端子音频矩阵应用领域
  • 【问题解决】git status中文文件名乱码
  • #laravel部署安装报错loadFactoriesFrom是undefined method #
  • 《深入 React 技术栈》
  • 2017-09-12 前端日报
  • css选择器
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • Redash本地开发环境搭建
  • Spring Cloud Feign的两种使用姿势
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 关于 Cirru Editor 存储格式
  • 关于extract.autodesk.io的一些说明
  • 来,膜拜下android roadmap,强大的执行力
  • 设计模式走一遍---观察者模式
  • 使用agvtool更改app version/build
  • 赢得Docker挑战最佳实践
  • 用Python写一份独特的元宵节祝福
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • (6)STL算法之转换
  • (C语言)输入自定义个数的整数,打印出最大值和最小值
  • (Note)C++中的继承方式
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (算法)N皇后问题
  • (一)WLAN定义和基本架构转
  • ***测试-HTTP方法
  • .NET CLR基本术语
  • .NET Core 2.1路线图
  • .net dataexcel winform控件 更新 日志
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .NET程序集编辑器/调试器 dnSpy 使用介绍
  • .net流程开发平台的一些难点(1)
  • .NET中的十进制浮点类型,徐汇区网站设计
  • ::before和::after 常见的用法
  • @hook扩展分析
  • @RequestBody的使用
  • @SuppressWarnings注解
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • [ C++ ] STL_list 使用及其模拟实现
  • [ 网络通信基础 ]——网络的传输介质(双绞线,光纤,标准,线序)
  • [android] 手机卫士黑名单功能(ListView优化)