Update: unified format

pull/369/head
Allan Downey 3 years ago
parent af1f6144a6
commit ab22f5febc

@ -42,9 +42,9 @@ fn main() {
我们的读者里不乏正义之士,此时肯定要质疑,一个简单的数组越界访问,为何要直接让程序崩溃?是不是有些小题大作了?
如果有过C语言的经验即使你越界了问题不大我依然尝试去访问至于这个值是不是你想要的(`100`号内存地址也有可能有值,只不过是其它变量或者程序的!),抱歉,不归我管,我只负责取,你要负责管理好自己的索引访问范围。上面这种情况被称为**缓冲区溢出**,并可能会导致安全漏洞,例如攻击者可以通过索引来访问到数组后面不被允许的数据。
如果有过C语言的经验即使你越界了问题不大我依然尝试去访问至于这个值是不是你想要的`100` 号内存地址也有可能有值,只不过是其它变量或者程序的!),抱歉,不归我管,我只负责取,你要负责管理好自己的索引访问范围。上面这种情况被称为**缓冲区溢出**,并可能会导致安全漏洞,例如攻击者可以通过索引来访问到数组后面不被允许的数据。
说实话我宁愿程序崩溃为什么当你取到了一个不属于你的值这在很多时候会导致程序上的逻辑bug! 有编程经验的人都知道这种逻辑上的bug是多么难被发现和修复!因此程序直接崩溃,然后告诉我们问题发生的位置,最后我们对此进行修复,这才是最合理的软件开发流程,而不是把问题藏着掖着:
说实话我宁愿程序崩溃为什么当你取到了一个不属于你的值这在很多时候会导致程序上的逻辑bug! 有编程经验的人都知道这种逻辑上的 BUG 是多么难被发现和修复!因此程序直接崩溃,然后告诉我们问题发生的位置,最后我们对此进行修复,这才是最合理的软件开发流程,而不是把问题藏着掖着:
```console
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
@ -75,7 +75,7 @@ note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose bac
上面的代码就是一次栈展开(也称栈回溯),它包含了函数调用的顺序,当然按照逆序排列:最近调用的函数排在列表的最上方。因为咱们的 `main` 函数基本是最先调用的函数了,所以排在了倒数第二位,还有一个关注点,排在最顶部最后一个调用的函数是 `rust_begin_unwind`,该函数的目的就是进行栈展开,呈现这些列表信息给我们。
要获取到栈回溯信息,你还需要开启 `debug` 标志,该标志在使用 `cargo run` 或者 `cargo build` 时自动开启(这两个操作默认是 `Debug` 运行方式). 同时,栈展开信息在不同操作系统或者 Rust 版本上也所有不同。
要获取到栈回溯信息,你还需要开启 `debug` 标志,该标志在使用 `cargo run` 或者 `cargo build` 时自动开启(这两个操作默认是 `Debug` 运行方式)。同时,栈展开信息在不同操作系统或者 Rust 版本上也所有不同。
## panic时的两种终止方式
当出现 `panic!` 时,程序提供了两种方式来处理终止流程: **栈展开** 和 **直接终止**。
@ -91,7 +91,7 @@ panic = 'abort'
## 线程 `panic` 后,程序是否会终止?
长话短说,如果是 `main` 线程,则程序会终止,如果是其它子线程,该线程会终止,但是不会影响 `main` 线程。因此,尽量不要在 `main` 线程中做太多任务,将这些任务交由子线程去做,就算子线程 `panic` 也不会导致整个程序的结束。
具体解析见[panic原理剖析](#panic原理剖析)
具体解析见 [panic原理剖析](#panic原理剖析)
## 何时该使用panic!
@ -149,9 +149,9 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
当调用 `panic!` 宏时,它会
1. 格式化 `panic` 信息,然后使用该信息作为参数,调用 `std::panic::panic_any()` 函数
2. `panic_any` 会检查应用是否使用了 `panic hook`,如果使用了,该 `hook` 函数就会被调用hook是一个钩子函数是外部代码设置的用于在panic触发时执行外部代码所需的功能
2. `panic_any` 会检查应用是否使用了 `panic hook`,如果使用了,该 `hook` 函数就会被调用(`hook` 是一个钩子函数,是外部代码设置的,用于在 `panic` 触发时,执行外部代码所需的功能)
3. 当 `hook` 函数返回后,当前的线程就开始进行栈展开:从 `panic_any` 开始,如果寄存器或者栈因为某些原因信息错乱了,那很可能该展开会发生异常,最终线程会直接停止,展开也无法继续进行
4. 展开的过程是一帧一帧的去回溯整个栈,每个帧的数据都会随之被丢弃,但是在展开过程中,你可能会遇到被用户标记为 `catching` 的帧(通过 `std::panic::catch_unwind()` 函数标记),此时用户提供的 `catch` 函数会被调用,展开也随之停止:当然,如果 `catch` 选择在内部调用 `std::panic::resume_unwind()` 函数,则展开还会继续。
4. 展开的过程是一帧一帧的去回溯整个栈,每个帧的数据都会随之被丢弃,但是在展开过程中,你可能会遇到被用户标记为 `catching` 的帧(通过 `std::panic::catch_unwind()` 函数标记),此时用户提供的 `catch` 函数会被调用,展开也随之停止:当然,如果 `catch` 选择在内部调用 `std::panic::resume_unwind()` 函数,则展开还会继续。
还有一种情况,在展开过程中,如果展开本身 `panic` 了,那展开线程会终止,展开也随之停止。

@ -1,7 +1,7 @@
# 可恢复的错误Result
还记得上一节中,提到的关于文件读取的思考题吧?当时我们解决了读取文件时遇到不可恢复错误该怎么处理的问题,现在来看看,读取过程中,正常返回和遇到可以恢复的错误时该如何处理。
假设,我们有一台消息服务器,每个用户都通过 websocket 连接到该服务器来接收和发送消息,该过程就涉及到 socket 文件的读写那么此时如果一个用户的读写发生了错误显然不能直接panic否则服务器会直接崩溃所有用户都会断开连接因此我们需要一种更温和的错误处理方式`Result<T,E>`。
假设,我们有一台消息服务器,每个用户都通过 websocket 连接到该服务器来接收和发送消息,该过程就涉及到 socket 文件的读写,那么此时,如果一个用户的读写发生了错误,显然不能直接 `panic`,否则服务器会直接崩溃,所有用户都会断开连接,因此我们需要一种更温和的错误处理方式:`Result<T, E>`。
之前章节有提到过,`Result<T, E>` 是一个枚举类型,定义如下:
```rust
@ -24,7 +24,7 @@ fn main() {
>
> 有几种常用的方式,此处更推荐第二种方法:
> - 第一种是查询标准库或者三方库文档,搜索 `File`,然后找到它的 `open` 方法
> - 在[Rust IDE](../../first-try/editor.md)章节,我们推荐了 `VSCode` IED和 `rust-analyzer` 插件,如果你成功安装的话,那么就可以在 `VScode` 中很方便的通过代码跳转的方式查看代码,同时 `rust-analyzer` 插件还会对代码中的类型进行标注,非常方便好用!
> - 在 [Rust IDE](../../first-try/editor.md) 章节,我们推荐了 `VSCode` IDE 和 `rust-analyzer` 插件,如果你成功安装的话,那么就可以在 `VSCode` 中很方便的通过代码跳转的方式查看代码,同时 `rust-analyzer` 插件还会对代码中的类型进行标注,非常方便好用!
> - 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你:
```rust
let f: u32 = File::open("hello.txt");
@ -44,7 +44,7 @@ error[E0308]: mismatched types
上面代码,故意将 `f` 类型标记成整形,编译器立刻不乐意了,你是在忽悠我吗?打开文件操作返回一个整形?来,大哥来告诉你返回什么:`std::result::Result<std::fs::File, std::io::Error>`,我的天呐,怎么这么长的类型!
别慌,其实很简单,首先 `Result` 本身是定义在 `std::result` 中的,但是因为 `Result` 很常用,所以就被包含在了[`prelude`](../../appendix/prelude.md)中(将常用的东东提前引入到当前作用域内),因此无需手动引入 `std::result::Result`,那么返回类型可以简化为 `Result<std::fs::File,std::io::Error>`,你看看是不是很像标准的 `Result<T,E>` 枚举定义?只不过 `T` 被替换成了具体的类型 `std::fs::File`,是一个文件句柄类型,`E` 被替换成 `std::io::Error`,是一个 IO 错误类型.
别慌,其实很简单,首先 `Result` 本身是定义在 `std::result` 中的,但是因为 `Result` 很常用,所以就被包含在了 [`prelude`](../../appendix/prelude.md) 中(将常用的东东提前引入到当前作用域内),因此无需手动引入 `std::result::Result`,那么返回类型可以简化为 `Result<std::fs::File,std::io::Error>`,你看看是不是很像标准的 `Result<T, E>` 枚举定义?只不过 `T` 被替换成了具体的类型 `std::fs::File`,是一个文件句柄类型,`E` 被替换成 `std::io::Error`,是一个 IO 错误类型.
这个返回值类型说明 `File::open` 调用如果成功则返回一个可以进行读写的文件句柄,如果失败,则返回一个 IO 错误:文件不存在或者没有访问文件的权限等。总之 `File::open` 需要一个方式告知调用者是成功还是失败,并同时返回具体的文件句柄(成功)或错误信息(失败),万幸的是,这些信息可以通过 `Result` 枚举提供:
```rust
@ -93,7 +93,7 @@ fn main() {
- 如果是文件不存在错误 `ErrorKind::NotFound`,就创建文件,这里创建文件`File::create` 也是返回 `Result`,因此继续用 `match` 对其结果进行处理:创建成功,将新的文件句柄赋值给 `f`,如果失败,则 `panic`
- 剩下的错误,一律 `panic`
虽然很清晰,但是代码还是有些啰嗦,我们会在[简化错误处理](../../errors/simplify.md)一章重点讲述如何写出更优雅的错误。
虽然很清晰,但是代码还是有些啰嗦,我们会在[简化错误处理](../../advance/errors/simplify.md)一章重点讲述如何写出更优雅的错误。
## 失败就 panic: unwrap 和 expect
上一节中,已经看到过这两兄弟的简单介绍,这里再来回顾下。
@ -202,7 +202,7 @@ let mut f = match f {
虽然 `?``match` 功能一致,但是事实上 `?` 会更胜一筹。何解?
想象一下,一个设计良好的系统中,肯定有自定义的错误特征,错误之间很可能会存在上下级关系,例如标准库中的 `std::io::Error ``std::error::Error`,前者是io相关的错误结构体,后者是一个最最通用的标准错误特征,同时前者实现了后者,因此 `std::io::Error` 可以转换为 `std:error::Error`
想象一下,一个设计良好的系统中,肯定有自定义的错误特征,错误之间很可能会存在上下级关系,例如标准库中的 `std::io::Error ``std::error::Error`,前者是 IO 相关的错误结构体,后者是一个最最通用的标准错误特征,同时前者实现了后者,因此 `std::io::Error` 可以转换为 `std:error::Error`
明白了以上的错误转换,`?` 的更胜一筹就很好理解了,它可以自动进行类型提升(转换):
```rust
@ -231,7 +231,7 @@ fn read_username_from_file() -> Result<String, io::Error> {
Ok(s)
}
```
瞧见没? `?` 还能实现链式调用,`File::open` 遇到错误就返回,没有错误就将 `Ok` 中的值取出来用于下一个方法调用,简直太精妙了,从 Go 语言过来的我,内心狂喜(其实学 Rust 的苦和痛我才不会告诉你们)
瞧见没? `?` 还能实现链式调用,`File::open` 遇到错误就返回,没有错误就将 `Ok` 中的值取出来用于下一个方法调用,简直太精妙了,从 Go 语言过来的我,内心狂喜(其实学 Rust 的苦和痛我才不会告诉你们)
不仅有更强,还要有最强,我不信还有人比我更短(不要误解)
```rust
@ -270,7 +270,7 @@ fn first(arr: &[i32]) -> Option<&i32> {
arr.get(0)
}
```
有一句话怎么说?没有需求,制造需求也要上。。。大家别跟我学习,这是软件开发大忌。只能用代码洗洗眼了:
有一句话怎么说?没有需求,制造需求也要上……大家别跟我学习,这是软件开发大忌。只能用代码洗洗眼了:
```rust
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()

Loading…
Cancel
Save