From 697e878cacd46f7ac53d1671f66be4d70e637a2d Mon Sep 17 00:00:00 2001 From: sunface Date: Mon, 6 Dec 2021 17:21:40 +0800 Subject: [PATCH] update --- assets/ferris.js | 1 - book.toml | 8 +- src/SUMMARY.md | 63 ++-- src/basic/base-type/index.md | 34 +++ src/basic/base-type/numbers.md | 270 ++++++++++++++++++ src/basic/base-type/others.md | 66 +++++ src/basic/base-type/string.md | 1 + src/basic/compound-type/array.md | 1 + src/basic/compound-type/enum.md | 1 + src/basic/compound-type/tuple.md | 1 + src/basic/intro.md | 7 + src/basic/type-converse.md | 29 ++ src/basic/type.md | 1 - src/basic/variable.md | 20 +- src/errors/panic-codes.md | 7 + src/first-try/hello-world.md | 110 ++++++- src/monitor/log.md | 8 + .../{observability => observability.md} | 0 src/style-guide/clippy.md | 1 + 19 files changed, 594 insertions(+), 35 deletions(-) create mode 100644 src/basic/base-type/index.md create mode 100644 src/basic/base-type/numbers.md create mode 100644 src/basic/base-type/others.md create mode 100644 src/basic/base-type/string.md create mode 100644 src/basic/compound-type/array.md create mode 100644 src/basic/compound-type/enum.md create mode 100644 src/basic/compound-type/tuple.md create mode 100644 src/basic/type-converse.md delete mode 100644 src/basic/type.md rename src/monitor/{observability => observability.md} (100%) create mode 100644 src/style-guide/clippy.md diff --git a/assets/ferris.js b/assets/ferris.js index 9bacc4ef..06a40cd7 100644 --- a/assets/ferris.js +++ b/assets/ferris.js @@ -36,7 +36,6 @@ function attachFerrises (type) { } function attachFerris (element, type) { - alert('1') var a = document.createElement('a') a.setAttribute('href', 'ch00-00-introduction.html#ferris') a.setAttribute('target', '_blank') diff --git a/book.toml b/book.toml index c3a70f9b..4456f4e9 100644 --- a/book.toml +++ b/book.toml @@ -9,6 +9,12 @@ additional-js = ["assets/ferris.js"] git-repository-url = "https://github.com/rustcollege" edit-url-template = "https://github.com/rustcollege/rust-course/edit/main/{path}" +[output.html.playground] +editable = true +copy-js = true +line-numbers = true + [output.html.fold] enable = true -level = 0 \ No newline at end of file +level = 0 + diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 7527e5c4..1dac2512 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -13,14 +13,20 @@ ## Rust学习三部曲 -- [基本语法](basic/intro.md) - - [变量与绑定](basic/variable.md) - - [基本类型(todo)](basic/type.md) +- [基本语法 doing](basic/intro.md) + - [变量绑定与结构](basic/variable.md) + - [基本类型](basic/base-type/index.md) + - [数值类型](basic/base-type/numbers.md) + - [字符串](basic/base-type/string.md) + - [字符、布尔、元类型](basic/base-type/others.md) - [复合类型(todo)](basic/compound-type/intro.md) - - [Struct(todo)](basic/compound-type/struct.md) + - [结构体(todo)](basic/compound-type/struct.md) + - [枚举](basic/compound-type/enum.md) + - [元组](basic/compound-type/tuple.md) + - [数组与切片](basic/compound-type/array.md) + - [类型转换](basic/type-converse.md) - [函数与方法(todo)](basic/function-method.md) - [格式化输出(todo)](basic/formatted-output.md) - - [字符串、数组与切片(todo)](basic/string-array-slice.md) - [流程控制(todo)](basic/flow-control.md) - [返回、异常和错误(todo)](basic/exception-error.md) - [模式匹配(todo)](basic/match-pattern.md) @@ -28,12 +34,12 @@ - [包和模块(todo)](basic/crate-module.md) - [语句与表达式(todo)](basic/statement-expression.md) -- [核心语法](core/intro.md) +- [核心语法 todo](core/intro.md) - [所有权(todo)](core/ownership.md) - [借用(todo)](core/borrowing.md) - [生命周期(todo)](core/lifetime.md) -- [进阶语法](advance/intro.md) +- [进阶语法 todo](advance/intro.md) - [泛型(todo)](advance/generitic.md) - [特征(todo)](advance/trait.md) - [迭代器(todo)](advance/interator.md) @@ -44,14 +50,14 @@ ## 专题内容,每个专题都配套一个小型项目进行实践 -- [错误处理](errors/intro.md) +- [错误处理 todo](errors/intro.md) - [panic!](errors/panic.md) - [适用Result返回错误](errors/result.md) - [自定义错误](errors/user-define.md) - [让错误输出更优雅](errors/pretty-format.md) - [会导致panic的代码](errors/panic-codes.md) - -- [Cargo详解](cargo/intro.md) + +- [Cargo详解 todo](cargo/intro.md) - [常用命令](cargo/commands.md) - [项目结构](cargo/layout.md) - [Cargo.toml和Cargo.lock](cargo/cargo-toml-lock.md) @@ -64,45 +70,45 @@ - [自定义构建脚本](cargo/build-js.md) - [Cargo profile](cargo/profile.md) -- [测试](test/intro.md) +- [测试 todo](test/intro.md) - [单元测试](test/unit.md) - [集成测试](test/intergration.md) - [性能测试](test/benchmark.md) - [持续集成](test/ci.md) -- [日志和监控](monitor/intro.md) +- [日志和监控 todo](monitor/intro.md) - [日志](monitor/log.md) - - [可观测性](monitor/observability) + - [可观测性](monitor/observability.md) - [监控(APM)](monitor/apm.md) -- [智能指针](smart-pointer/intro.md) +- [智能指针 todo](smart-pointer/intro.md) - [Box对象(todo)](smart-pointer/box.md) - [Deref和Drop特征(todo)](smart-pointer/deref-drop.md) - [Rc与RefCell(todo)](smart-pointer/rc-refcell.md) - [自引用与内存泄漏(todo)](smart-pointer/self-referrence.md) -- [常见特征解析](traits/intro.md) +- [常见特征解析 todo](traits/intro.md) - [类型转换From/Into](traits/from-into.md) - [AsRef, AsMut](traits/as-ref-as-mut.md) - [Borrow, BorrowMut, ToOwned](traits/borrow-family.md) - [Deref和引用隐式转换](traits/deref.md) - [写时拷贝Cow](traits/cow.md) -- [多线程](multi-threads/intro.md) +- [多线程 todo](multi-threads/intro.md) - [线程管理(todo)](multi-threads/thread.md) - [消息传递(todo)](multi-threads/message-passing.md) - [数据共享Arc、Mutex、Rwlock(todo)](multi-threads/ref-counter-lock.md) - [数据竞争(todo)](multi-threads/races.md) - [Send、Sync(todo)](multi-threads/send-sync.md) -- [深入内存](memory/intro.md) +- [深入内存 todo](memory/intro.md) - [指针和引用(todo)](memory/pointer-ref.md) - [未初始化内存(todo)](memory/uninit.md) - [内存分配(todo)](memory/allocation.md) - [内存布局(todo)](memory/layout.md) - [虚拟内存(todo)](memory/virtual.md) -- [网络和异步编程](networking/intro.md) +- [网络和异步编程 todo](networking/intro.md) - [TCP和网络原理(todo)](networking/tcp.md) - [并发与并行(todo)](networking/concurrency-parallelism.md) - [异步编程](networking/async/intro.md) @@ -120,37 +126,38 @@ - [基本用法](networking/async/tokio/basic.md) - [异步消息流](networking/async/tokio/stream.md) -- [代码规范](style-guide/intro.md) +- [代码规范 doing](style-guide/intro.md) - [命名规范](style-guide/naming.md) - [代码风格(todo)](style-guide/code.md) - -- [面向对象](object-oriented/intro.md) + - [Clippy](style-guide/clippy.md) + +- [面向对象 todo](object-oriented/intro.md) - [为何OO(todo)](object-oriented/characteristics.md) - [特征对象](object-oriented/trait-object.md) - [设计模式](object-oriented/design-pattern.md) -- [不安全Rust](unsafe/intro.md) +- [不安全Rust todo](unsafe/intro.md) - [原生指针(todo)](unsafe/raw-pointer.md) - [修改全局变量](unsafe/modify-global-var.md) - [FFI外部语言用](unsafe/ffi.md) -- [对抗编译检查](fight-with-compiler/intro.md) +- [对抗编译检查 todo](fight-with-compiler/intro.md) - [幽灵数据(todo)](fight-with-compiler/phantom-data.md) - [生命周期(todo)](fight-with-compiler/lifetime.md) - [类型未限制(todo)](fight-with-compiler/unconstrained.md) -- [宏编程](macro/intro.md) +- [宏编程 todo](macro/intro.md) - [过程宏(todo)](macro/procedure-macro.md) -- [性能调优](performance/intro.md) +- [性能调优 todo](performance/intro.md) - [Benchmark性能测试(todo)](performance/benchmark.md) - [减少Runtime check(todo)](performance/runtime-check.md) -- [标准库解析](std/intro.md) +- [标准库解析 todo](std/intro.md) - [如何寻找你想要的内容](std/search.md) -- [常用三方库](libraries/intro.md) +- [常用三方库 todo](libraries/intro.md) - [JSON](libraries/json/intro.md) - [serde(todo)](libraries/json/serde.md) - [HTTP](libraries/http/intro.md) @@ -159,7 +166,7 @@ - [structopt(todo)](libraries/command/structopt.md) ## 场景模版 -- [场景模版](templates/intro.md) +- [场景模版 todo](templates/intro.md) - [文件操作](templates/files/intro.md) - [目录(todo)](templates/files/dir.md) - [Http请求(todo)](templates/http/intro.md) diff --git a/src/basic/base-type/index.md b/src/basic/base-type/index.md new file mode 100644 index 00000000..e6f1e503 --- /dev/null +++ b/src/basic/base-type/index.md @@ -0,0 +1,34 @@ +# 基本类型 + +基本数据类型在Rust中是最最常见的数据类型,基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),基本类型由以下组成: + +- 数值类型: 有符号整数 (`i8`, `i16`, `i32`, `i64`, `isize`)、 无符号整数 (`u8`, `u16`, `u32`, `u64`, `usize`) 、浮点数 (`f32`, `f64`)、以及有理数、复数 +- 字符串:字符串字面量、字符串切片&str和堆分配字符串String +- 布尔类型: `true`和`false` +- 字符类型: 表示单个Unicode字符,存储为4个字节 +- 元类型: 即`()`,其唯一的值也是`()` + + +## 类型推导与标注 + +与python、js等动态语言不同,Rust是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的数据类型,但这不意味着你需要为你的每个变量指定类型,因为**Rust编译器很聪明,它可以根据变量的值和使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要我们手动去给予一个类型标注,关于这一点在[Rust语言初印象](../first-try/hello-world.md#Rust语言初印象)中有过展示. + +再比如以下代码(引用自Rust官方编程那本书): + +```rust +let guess = "42".parse().expect("Not a number!"); +``` + +如果这里不添加类型标注,则编译器会报错: +```console +$ cargo build + Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations) +error[E0282]: type annotations needed + --> src/main.rs:2:9 + | +2 | let guess = "42".parse().expect("Not a number!"); + | ^^^^^ consider giving `guess` a type +``` + +意味着,我们需要提供给编译器更多的信息,例如给`guess`变量一个显示的类型标注: `let guess:i32 = ...` 或者`"42".parse::()`. + diff --git a/src/basic/base-type/numbers.md b/src/basic/base-type/numbers.md new file mode 100644 index 00000000..65f4e541 --- /dev/null +++ b/src/basic/base-type/numbers.md @@ -0,0 +1,270 @@ +# 数值类型 + +计算机和数值关联在一起的时间,远比你想象的要长,因此数值类型可以说是有计算机以来就有的类型,下面内容将深入讨论Rust的数值类型以及相关的运算符。 + +## 整数和浮点数 + +Rust使用一个相对传统的语法来创建整数(`1`,`2`,...)和浮点数(`1.0`,`1.1`,...)。整数、浮点数的运算和你在其它语言上见过的一致,都是通过常见的运算符来完成。 + +> 不仅仅是数值类型,Rust也允许在复杂类型上定义运算符,例如在自定义类型上定义`+`运算符,这种行为被称为运算符重载, Rust具体支持的可重载运算符见[这里](../../appendix/operators.md#运算符) + +#### 整数类型 + +**整数**是没有是没有小数部分的数字。在之前,我们使用过`i32`类型, 表示有符号的32位整数(`i` 是英文单词 *integer* 的首字母,与之相反的是 `u`,代表无符号 `unsigned` 类型)。下表显示了 Rust 中的内置的整数类型, 在有符号和和无符号的列中(例如 *i16*)的每个定义形式都可用于声明整数类型。 + +Rust 中的整数类型 + +| 长度 | 有符号类型 | 无符号类型 | +|------------|---------|---------| +| 8 位 | `i8` | `u8` | +| 16 位 | `i16` | `u16` | +| 32 位 | `i32` | `u32` | +| 64 位 | `i64` | `u64` | +| 128-位 | `i128` | `u128` | +| 视架构而定 | `isize` | `usize` | + +类型定义的形式统一为:有无符号 + 类型大小(位数)。**无符号数**表示数字只能取正数,而**有符号**则表示数字即可以取正数又可以取负数。就像在纸上写数字一样:当要强调符号时,数字前面可以带上正号或负号;然而,当很明显确定数字为正数时,就不需要加上正号了。有符号数字以[二进制补码](https://en.wikipedia.org/wiki/Two%27s_complement)形式存储。 + +每个有符号类型规定的数字范围是 -(2n - 1) ~ 2n - +1 - 1,其中 `n` 是该定义形式的位长度。所以 `i8` 可存储数字范围是 -(27) ~ 27 - 1,即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2n - 1,所以 `u8` 能够存储的数字为 0 ~ 28 - 1,即 0 ~ 255。 + +此外,`isize` 和 `usize` 类型取决于程序运行的计算机cpu类型: 若cpu是32位的,则这两个类型是32位的,同理,若cpu是64位,那么它们则是64位64位. + + +整型的字面量可以可以写成下表 3-2 中任意一种。注意,除了字节字面量之外的所有的数字字面量都允许使用类型后缀,例如 `57u8`,还有可以使用 `_` 作为可视分隔符,如 `1_000`。 + +表 3-2: Rust 的整型字面量 + +| 数字字面量 | 示例 | +|------------------|---------------| +| 十进制 | `98_222` | +| 十六进制 | `0xff` | +| 八进制 | `0o77` | +| 二进制 | `0b1111_0000` | +| 字节 (仅限于 `u8`) | `b'A'` | + +那么该使用哪种类型的整型呢?如果不确定,Rust 的默认值通常是个不错的选择,整型默认是 `i32`:这通常是最快的,即便在 64 位系统上也是。`isize` 和 `usize` 的主要应用场景是用作某些集合的索引。 + +> ##### 整型溢出 +> +> 比方说有一个 `u8` ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 会检查整型溢出若存在这些问题则使程序在编译时 *panic*。Rust 使用这个术语来表明程序因错误而退出。 [该章节](../../errors/panic.md)会详细介绍 panic。 +> +> 在当使用 `--release` 参数进行 release 模式构建时,Rust **不**检测溢出。相反当检测到整型溢出时,Rust 会进行一种被称为二进制补码的方式进行(*two’s complement wrapping*)操作。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 `u8` 的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是你期望的值。依赖整型溢出包裹的行为不是一种正确的做法。 +> +> 要显式处理溢出的可能性,可以使用标准库针对原始数字类型提供的以下的一系列方法: +> +> - 使用 `wrapping_*` 方法在所有模式下进行包裹,例如 `wrapping_add` +> - 如果使用 `checked_*` 方法时发生溢出,则返回 `None` 值 +> - 使用 `overflowing_*` 方法返回该值和一个指示是否存在溢出的布尔值 +> - 使用 `saturating_*` 方法使值达到最小值或最大值 + +#### 浮点类型 + +**浮点类型数字** 是带有小数点的数字,在 Rust 中浮点类型数字也有两种基本类型: `f32` 和 `f64`,分别为 32 位和 64 位大小。默认浮点类型是 `f64`,因为在现代的 CPU 中它的速度与 `f32` 几乎相同,但精度更高。 + +下面是一个演示浮点数的示例: + +```rust +fn main() { + let x = 2.0; // f64 + + let y: f32 = 3.0; // f32 +} +``` + +浮点数根据 IEEE-754 标准表示。`f32` 类型是单精度浮点型,`f64` 为双精度。 + +#### 数字运算 + +Rust 支持所有数字类型的基本数学运算:加法、减法、乘法、除法和取模运算。下面代码演示了各使用一条 `let` 语句来说明相应运算的用法: + + +```rust +fn main() { + // 加法 + let sum = 5 + 10; + + // 减法 + let difference = 95.5 - 4.3; + + // 乘法 + let product = 4 * 30; + + // 除法 + let quotient = 56.7 / 32.2; + + // 求余 + let remainder = 43 % 5; +} +``` + +这些语句中的每个表达式都使用了数学运算符,并且计算结果为一个值,然后绑定到一个变量上。[附录](../../appendix/operators.md)中给出了 Rust 提供的所有运算符的列表。 + + +再来看一个综合性的示例: + +```rust +fn main() { + // 编译器会进行自动推导,给予twenty i32的类型 + let twenty = 20; + // 类型标注 + let twenty_one: i32 = 21; + // 通过类型后缀的方式进行类型标注:22是i32类型 + let twenty_two = 22i32; + + // 只有同样类型,才能运算 + let addition = twenty + twenty_one + twenty_two; + println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition); + + // 对于较长的数字,可以用_进行分割,提升可读性 + let one_million: i64 = 1_000_000; + println!("{}", one_million.pow(2)); + + // 定义一个f32数组,其中42.0会自动被推导为f32类型 + let forty_twos = [ + 42.0, + 42f32, + 42.0_f32, + ]; + + // 打印数组中第一个值,其中控制小数位为2位 + println!("{:02}", forty_twos[0]); + } + ``` + +#### 浮点数陷阱 + +浮点数由于底层格式的特殊性,导致了如果在使用浮点数时不够谨慎,就可能造成危险,有两个原因: +1. **浮点数往往是你想要数字的近似表达** +浮点数类型是基于二进制实现的,但是我们想要计算的数字往往是基于十进制,例如0.1在二进制上并不存在精确的表达形式,但是在十进制上就存在。这种不匹配性导致一定的歧义性,更多的,虽然浮点数能代表真实的数值,但是由于底层格式问题,它往往受限于定长的浮点数精度,如果你想要表达完全精准的真实数字,只有使用无限精度的浮点数才行 + +2. **浮点数在某些特性上是反直觉的** +例如你觉得浮点数可以进行比较,对吧?是的,它们确实可以使用`>`,`>=`等进行比较,但是在某些场景下,这种直觉上的比较特性反而会害了你。因为`f32`,`f64`上的比较运算实现的是`std::cmp::PartialEq`[特征](../../advance/trait.md), 但是并没有实现`std::cmp::Eq`特征,但是后者在其它数值类型上都有定义,说了这么多,可能大家还是云里雾里,用一个例子来举例: + +Rust的HashMap数据结构,是一个KV类型的hash map实现,它对于K没有特定类型的限制,但是要求能用作K的类型必须实现了`std::cmp::Eq`特征,因为这意味着你无法使用浮点数作为HashMap的Key,来存储键值对,但是作为对比,Rust的整数类型、字符串类型、布尔类型都实现了该特征,因此可以作为HashMap的Key。 + +为了避免上面说的两个陷阱,你需要遵守以下准则: +- 避免在浮点数上测试相等性 +- 当结果在数学上可能存在未定义时,需要格外的小心 + +来看个小例子: +```rust +fn main() { + // 断言0.1 + 0.2与0.3相等 + assert!(0.1 + 0.2 == 0.3); +} +``` + +你可能以为,这段代码没啥问题吧,实际上它会panic(程序崩溃,抛出异常),因为二进制精度问题,导致了0.1 + 0.2并不严格等于0.3,它们可能在小数点N位后存在误差。 + +那如果非要进行比较呢?可以考虑用这种方式`(0.1 + 0.2 - 0.3).abs() < 0.00001`,具体小于多少,取决于你对精度的需求. + +讲到这里,相信大家基本已经明白了,为什么操作浮点数时要格外的小心,但是还不够,下面再来一段代码,直接震撼你的灵魂: + +```rust +fn main() { + let abc: (f32, f32, f32) = (0.1, 0.2, 0.3); + let xyz: (f64, f64, f64) = (0.1, 0.2, 0.3); + + println!("abc (f32)"); + println!(" 0.1 + 0.2: {:x}", (abc.0 + abc.1).to_bits()); + println!(" 0.3: {:x}", (abc.2).to_bits()); + println!(); + + println!("xyz (f64)"); + println!(" 0.1 + 0.2: {:x}", (xyz.0 + xyz.1).to_bits()); + println!(" 0.3: {:x}", (xyz.2).to_bits()); + println!(); + + assert!(abc.0 + abc.1 == abc.2); + assert!(xyz.0 + xyz.1 == xyz.2); + } +``` + +运行该程序,输出如下: + +```console +abc (f32) + 0.1 + 0.2: 3e99999a + 0.3: 3e99999a + +xyz (f64) + 0.1 + 0.2: 3fd3333333333334 + 0.3: 3fd3333333333333 + +thread 'main' panicked at 'assertion failed: xyz.0 + xyz.1 == xyz.2', +➥ch2-add-floats.rs.rs:14:5 +note: run with `RUST_BACKTRACE=1` environment variable to display +➥a backtrace +``` + +仔细看,对`f32`类型做加法时,`0.1+0.2`的结果是`3e99999a`,0.3也是`3e99999a`,因此`f32`下的`0.1+0.2=0.3`通过测试,但是到了`f64`类型时,结果就不一样了,因为f64精度高很多,因此在小数点非常后面发生了一点微小的变化,`0.1+0.2`以`4`结尾,但是0.3以`3`结尾,因此`f64`下的测试失败了,并且抛出了异常。 + +是不是**blow your mind away**? 没关系,在本书的后续章节中类似的直击灵魂的代码还很多,这就是我们敢号称`Rust语言圣经`的勇气! + +#### NaN + +对于数学上未定义的结果,例如对负数取平方根`-42.1.sqrt()`,会产生一个特殊的结果:Rust的浮点数类型使用NaN(not a number)来处理这些情况。 + +**所有跟`NaN`交互的操作,都会返回一个`NaN`**,而且`NaN`不能用来比较,下面的代码会崩溃: + +```rust +fn main() { + let x = (-42.0_f32).sqrt(); + assert_eq!(x, x); +} +``` + +出于防御性编程的考虑,可以使用`is_nan()`等方法,可以用来判断一个数值是否是`NaN`: + +```rust +fn main() { + let x = (-42.0_f32).sqrt(); + if x.is_nan() { + println!("未定义的数学行为") + } +} +``` + +## 有理数和复数 + +Rust的标准库相比其它语言,对于准入的门槛较高,因此有理数和复数并未包含在标准库中: +- 有理数和复数对应的数据库 +- 任意大小的整数和任意精度的浮点数 +- 固定精度的十进制小数,常用于货币相关的场景 + +好在社区已经开发出高质量的Rust数值库:[num](https://crates.io/crates/num). + +按照以下步骤来引入`num`库: +1. 创建新工程`cargo new complex-num && cd complex-num` +2. 在`Cargo.toml`中的`[dependencies]`下添加一行`num = 0.4` +3. 将`src/main.rs`文件中的`main`函数替换为下面的代码 +4. 运行`cargo run` + +```rust +use num::complex::Complex; + + fn main() { + let a = Complex { re: 2.1, im: -1.2 }; + let b = Complex::new(11.1, 22.2); + let result = a + b; + + println!("{} + {}i", result.re, result.im) + } +``` + + +## 总结 + +之前提到了过Rust的数值类型和运算跟其他语言较为相似,但是实际上,除了语法上的不同之外,还是存在一些差异点: + +- **Rust拥有相当多的数值类型**. 因此你需要熟悉这些类型所占用的字节数,这样就知道该类型允许的大小范围以及你选择的类型是否能表达负数 +- **类型转换必须是显式的**. Rust永远也不会偷偷把你的16bit整数转换成32bit整数 +- **Rust的数值上可以使用方法**. 例如你可以用以下方法来将24.5取整: `13.14_f32.round()`, 在这里我们使用了类型后缀,因为编译器需要知道`13.14`的具体类型 + +数值类型的讲解已经基本结束,接下来来看看字符串。 + + + + + \ No newline at end of file diff --git a/src/basic/base-type/others.md b/src/basic/base-type/others.md new file mode 100644 index 00000000..5f8a12d1 --- /dev/null +++ b/src/basic/base-type/others.md @@ -0,0 +1,66 @@ +# 字符、布尔、元类型 + +这三个类型所处的地位比较尴尬,你说它们重要吧,在需要的时候也是不可或缺,说它们不重要吧,确实出现的身影不是很多,而且这三个类型都有一个共同点:简单,因此我们统一放在一起讲。 + +## 字符类型(char) + +字符,对于没有其它编程经验的新手来说可能不太好理解(没有编程经验敢来学Rust的绝对是好汉),如果用字母来表示就很好理解了。下文我们用`char`来指代字符。 + +下面的代码展示了几个颇具异域风情的字符: +``` +fn main() { + let c = 'z'; + let z = 'ℤ'; + let g = '国'; + let heart_eyed_cat = '😻'; +} +``` + +如果你从部分陈旧的语言来,可能会大喊一声:这XX叫字符?是的,在Rust语言中这些都是字符,Rust的字符不仅仅是`ASCII`,所有的`Unicode`值都可以作为Rust字符,包括中文/日文/韩文,emoji表情等等,都是合法的字符类型。Unicode 值的范围从 U+0000~U+D7FF 和 U+E000~U+10FFFF。不过“字符”并不是 Unicode 中的一个概念,所以人在直觉上对“字符”的理解和 Rust 的字符概念并不一致。 + +由于Unicode都是4个字节编码,因此字符类型也是占用4个字节: +```rust +fn main() { + let x = '中'; + println!("字符'中'占用了{}字节的内存大小",std::mem::size_of_val(&x)); +} +``` + +输出如下: + +```console +$ cargo run + Compiling ... + +字符'中'占用了4字节的内存大小 +``` + +> 注意,我们还没开始讲字符串,但是这里提前说一下,和一些语言不同,Rust的字符只能用`''`来表示,`""`是留给字符串的 + +## 布尔(bool) + +Rust 中的布尔类型有两个可能的值:`true` 和 `false`。布尔值占用内存的大小为 1 个字节。布尔类型使用 `bool` 指定。例如: + +```rust +fn main() { + let t = true; + + let f: bool = false; // 使用类型标注,显式指定f的类型 + + if f { + println!("这是段毫无意义的代码"); + } +} +``` + +使用布尔类型的场景主要在于控制流,例如上述代码的中的`if`就是其中之一。 + +## 元类型 + +元类型就是`()`,对,你没看错,就是`()`,唯一的值也是`()`,可能读者读到这里就不愿意了,你也太敷衍了吧,管这叫类型? + +只能说,再不起眼的东西,都有其用途,在目前为止的学习过程中,大家已经看到过很多次`fn main()`函数的使用吧?那么这个函数返回什么呢? +没错就是这个元类型`()`,你不能说`main`函数无返回值,因为没有返回值的函数在Rust中是有单独的定义的:`发散函数`,顾名思义,无法收敛的函数. + +例如常见的`println!()`的返回值也是`()`。 + diff --git a/src/basic/base-type/string.md b/src/basic/base-type/string.md new file mode 100644 index 00000000..97f10b2d --- /dev/null +++ b/src/basic/base-type/string.md @@ -0,0 +1 @@ +# 字符与字符串 diff --git a/src/basic/compound-type/array.md b/src/basic/compound-type/array.md new file mode 100644 index 00000000..2d531bf7 --- /dev/null +++ b/src/basic/compound-type/array.md @@ -0,0 +1 @@ +# 数组 diff --git a/src/basic/compound-type/enum.md b/src/basic/compound-type/enum.md new file mode 100644 index 00000000..608029dd --- /dev/null +++ b/src/basic/compound-type/enum.md @@ -0,0 +1 @@ +# 枚举 diff --git a/src/basic/compound-type/tuple.md b/src/basic/compound-type/tuple.md new file mode 100644 index 00000000..6ee1b27c --- /dev/null +++ b/src/basic/compound-type/tuple.md @@ -0,0 +1 @@ +# 元组 diff --git a/src/basic/intro.md b/src/basic/intro.md index cc394a98..b36e2fb9 100644 --- a/src/basic/intro.md +++ b/src/basic/intro.md @@ -1,5 +1,12 @@ # Rust基本概念 +从现在开始,你已经正式踏入了Rust大陆,这篇广袤而神秘的世界,在这个世界中,你将接触到很多之前你都没有听过的概念: +- 所有权、借用、生命周期 +- 宏编程 +- 模式匹配 + +类似的还有很多,不过不用怕,一方面,这本书会带你彻底探索这个神秘的大陆,另一方面,我们也有一个非常友善的社区,在里面你可以提问也可以分享自己的所学所思: [社区网址](https://community.college.rs). + 本章主要介绍Rust的基础语法、数据类型、项目结构等,学完本章,你将对Rust代码有一个清晰、完整的认识。 在基本概念方面,Rust和其它语言并没有大的区别,它拥有变量、数据、函数等等。 diff --git a/src/basic/type-converse.md b/src/basic/type-converse.md new file mode 100644 index 00000000..76bc9530 --- /dev/null +++ b/src/basic/type-converse.md @@ -0,0 +1,29 @@ +# 类型转换 + +In some cases, using the as keyword is too restrictive. It’s possible to regain fuller control over the type conversion process at the cost of introducing some bureaucracy. The following listing shows a Rust method to use instead of the as keyword when the conversion might fail. + +```rust +use std::convert::TryInto; + + fn main() { + let a: i32 = 10; + let b: u16 = 100; + + let b_ = b.try_into() + .unwrap(); + + if a < b_ { + println!("Ten is less than one hundred."); + } + } + ``` + + Listing 2.5 introduces two new Rust concepts: traits and error handling. On line 1, the use keyword brings the std::convert::TryInto trait into local scope. This unlocks the try_into() method of the b variable. We’ll bypass a full explanation of why this occurs for now. In the meantime, consider a trait as a collection of methods. If you are from an object-oriented background, traits can be thought of as abstract classes or interfaces. If your programming experience is in functional languages, you can think of traits as type classes. + +Line 7 provides a glimpse of error handling in Rust. b.try_into() returns an i32 value wrapped within a Result. Result is introduced properly in chapter 3. It can contain either a success value or an error value. The unwrap() method can handle the success value and returns the value of b as an i32 here. If the conversion between u16 and i32 were to fail, then calling unsafe() would crash the program. As the book progresses, you will learn safer ways of dealing with Result rather than risking the program’s stability! + +A distinguishing characteristic of Rust is that it only allows a type’s methods to be called when the trait is within local scope. An implicit prelude enables common operations such as addition and assignment to be used without explicit imports. + +> TIP +> +> To understand what is included in local scope by default, you should investigate the std::prelude module. Its >documentation is available online at https://doc.rust-lang.org/std/prelude/index.html. \ No newline at end of file diff --git a/src/basic/type.md b/src/basic/type.md deleted file mode 100644 index 22011c1a..00000000 --- a/src/basic/type.md +++ /dev/null @@ -1 +0,0 @@ -# 基本类型 \ No newline at end of file diff --git a/src/basic/variable.md b/src/basic/variable.md index 0d0e4e32..6c44fe9d 100644 --- a/src/basic/variable.md +++ b/src/basic/variable.md @@ -1,4 +1,4 @@ -# 变量与绑定 +# 变量绑定与解构 > 本节在内容上部分参考了[Rust Book](https://doc.rust-lang.org/stable/book/) @@ -99,6 +99,22 @@ The value of x is: 6 例如,在使用大型数据结构或者热点代码路径(被大量频繁调用)的情形下,在同一内存位置更新实例可能比复制并返回新分配的实例要更快。使用较小的数据结构时,通常创建新的实例并以更具函数式的风格来编写程序,可能会更容易理解,所以值得以较低的性能开销来确保代码清晰。 +### 变量解构 + +let表达式不仅仅用于变量的绑定,还能进行复杂变量的解构:从一个相对复杂的变量中,匹配出该变量的一部分内. + +```rust +fn main() { + let (a, mut b): (bool,bool) = (true, false); + // a = true,不可变; b = false,可变 + println!("a = {:?}, b = {:?}", a, b); + + b = true; + assert_eq!(a, b); +} +``` + + ### 变量和常量之间的差异 变量的值不能更改可能让你想起其他另一个很多语言都有的编程概念:**常量**(*constant*)。与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异。 @@ -189,4 +205,4 @@ error: aborting due to previous error 该错误标明,我们试图把一个`usize`类型的数值赋值为一个字符串变量。 -至此,关于变量的内容你已经彻底掌握了,下面来学习下Rust的基本数据类型。 \ No newline at end of file +至此,关于变量的内容你已经彻底掌握了,下面来学习下Rust的数据类型。Rust每个值都有其确切的数据类型, 总的来说可以分为两类:基本类型和复合类型, 先来看看基本类型。 \ No newline at end of file diff --git a/src/errors/panic-codes.md b/src/errors/panic-codes.md index 92588ae8..e717b592 100644 --- a/src/errors/panic-codes.md +++ b/src/errors/panic-codes.md @@ -1 +1,8 @@ # 会导致panic的代码 + + +> +> 比方说有一个 `u8` ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 会检查整型溢出若存在这些问题则使程序在编译时 *panic*。Rust 使用这个术语来表明程序因错误而退出。 [该章节](../../errors/panic.md)会详细介绍 panic。 +> +> 在当使用 `--release` 参数进行 release 模式构建时,Rust **不**检测溢出。相反当检测到整型溢出时,Rust 会进行一种被称为二进制补码的方式进行(*two’s complement wrapping*)操作。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 `u8` 的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是你期望的值。依赖整型溢出包裹的行为不是一种正确的做法。 +> \ No newline at end of file diff --git a/src/first-try/hello-world.md b/src/first-try/hello-world.md index d5af053e..91562bb4 100644 --- a/src/first-try/hello-world.md +++ b/src/first-try/hello-world.md @@ -1,9 +1,115 @@ -# 从hello world开始 +# 不仅仅是Hello world +几乎所有教程中安装的最后一个环节都是`hello world`,我们也不能免俗,但是在`hello world`之后,还有一个相亲,阿呸,Rust初印象环节,希望大家喜欢。 + +## 多国语言的"世界,你好" + +还记得我们之前讲到的[VScode](./editor.md) IDE和通过Cargo创建的[世界,你好](./cargo.md)工程吧? + +现在使用VScode打开我们在[上一节](./cargo.md)中创建的`world_hello`工程, 然后打开main.rs文件,这是当前Rust工程的入口文件,和其它语言没有区别。 + +将该文件内容修改为: +```rust +fn greet_world() { + let southern_germany = "Grüß Gott!"; + let chinese = "世界,你好"; + let english = "World, hello"; + let regions = [southern_germany, chinese, english]; + for region in regions.iter() { + println!("{}", ®ion); + } + } + + fn main() { + greet_world(); + } +``` + +然后打开终端,进入`world_hello`工程根目录,运行该程序(你也可以在VScode中打开终端,方法是点击左下角的错误和警告图标),可以看到来自世界各地的热情招呼: +```console +$ cargo run + Compiling world_hello v0.1.0 (/Users/sunfei/development/rust/world_hello) + Finished dev [unoptimized + debuginfo] target(s) in 0.21s + Running `target/debug/world_hello` +Grüß Gott! +世界,你好 +World, hello +sunfei@sunfeideMa +``` + +花点时间来看看上面的代码,首先,Rust原生支持UTF-8编码的字符串,这意味着你可以很容易的使用其它语言作为字符串内容。 + +其次,关注下`println`后面的`!`,如果你有Ruby编程经验,那么你可能会认为这是解构操作符,但是在Rust中,这是`宏`操作符,你目前可以认为宏是一种特殊类型函数。对于`println`来说,我们没有使用其它语言惯用的`%s`,`%d`来做输出占位符,而是使用{},因为Rust在底层帮我们做了大量工作,会自动识别输出数据的类型,例如当前例子,会识别为`string`类型。 + +最后,和其它语言不同,rust的集合类型不能直接进行循环,需要变成迭代器(这里是通过`.iter()`方法)后,才能用于迭代循环,在目前来看,你会觉得这一点好像挺麻烦,不急,以后就知道这么做的好处所在. + +至于函数声明、调用、数组的使用,和其它语言没什么区别,so easy! -在开始之前,我们来简单来个Rust的第一印象评测。 ## Rust语言初印象 Rust这门语言对于Haskell和Java开发来说,会觉得很熟悉,因为它们在高阶表达方面都很优秀,简而言之,可以很简洁的写出原本需要一大堆代码才能表达的含义,但是Rust又有所不同:它的性能是底层语言级别的性能,可以跟C/C++相媲美。 +在上一节,咱们学习了非常简单的Rust入门代码,现在来点复杂的,然后说说你对Rust的初印象。 + +下面的例子是关于基础文本处理: +```rust +fn main() { + let penguin_data = "\ + common name,length (cm) + Little penguin,33 + Yellow-eyed penguin,65 + Fiordland penguin,60 + Invalid,data + "; + + let records = penguin_data.lines(); + + for (i, record) in records.enumerate() { + if i == 0 || record.trim().len() == 0 { + continue; + } + + // 声明一个fields变量,类型是Vec + // Vec是vector的缩写,是一个可伸缩的集合类型,可以认为是一个动态数组 + // <_>表示Vec中的元素类型由编译器自行推断,在很多场景下,都会帮我们省却不少功夫 + let fields: Vec<_> = record + .split(',') + .map(|field| field.trim()) + .collect(); + if cfg!(debug_assertions) { + // 输出到标准错误输出 + eprintln!("debug: {:?} -> {:?}", + record, fields); + } + + let name = fields[0]; + // 1. 尝试把fields[1]的值转换为f32类型的浮点数,如果成功,则把f32值赋给length变量 + // 2. if let是一个匹配表达式,用来从=右边的结果中,匹配出length的值: + // 当=右边的表达式执行成功,则会返回一个Ok(f32)的类型,若失败,则会返回一个Err(e)类型,if let的作用就是仅匹配Ok也就是成功的情 + // 况,如果是错误,就直接忽略,同时if let还会做一次解构匹配,通过Ok(length)去匹配右边的Ok(f32),最终把相应的f32值赋给length + // 3. 当然你也可以忽视成功的情况,用if let Err(e) = fields[1].parse::() {...}匹配出错误,然后打印出来,但是没啥卵用 + if let Ok(length) = fields[1].parse::() { + // 输出到标准输出 + println!("{}, {}cm", name, length); + } + } + } +``` + +上面代码中值得注意的Rust特性有: +- 控制流:`for`和`continue`在一起,实现的循环 +- 方法语法:由于Rust没有继承,因此Rust不是传统意义上的面向对象语言,但是它却从OO语言那里偷师了方法的使用`record.trim()`,`record.split(',')`等 +- 高阶函数编程: 函数可以作为参数也能作为返回值,例如`.map(|field| field.trim())`, 这里`map`使用闭包函数作为参数,也可以称呼为`匿名函数`、`lambda函数` +- 类型标注: `if let Ok(length) = fields[1].parse::()`, 通过`::`的使用,告诉编译器`length`是一个`f32`类型的浮点数,这种类型标注不是很常用,但是在编译器无法推断出你的数据类型时,就很有用了 +- 条件编译: `if cfg!(debug_assertions)`,说明下面的输出打印只在`debug`模式下生效 +- 隐式返回:Rust提供了`return`关键字用于函数返回,但是在很多时候,我们可以省略它。因为Rust是[**基于表达式的语言**](../basic/statement-expression.md) + + +在终端中运行上述代码时,你会看到很多`debug: ...`的输出,你也已经了解,这是`条件编译`, 那么该怎么消除掉这些输出呢? + +读者大大肯定都很聪明,已经想到了:是的,在[认识Cargo](./cargo.md#手动编译和运行项目)中,曾经介绍过`--relese`参数,因为cargo run默认是运行的debug模式,如果想要消灭那些`debug:`输出,我们需要更改为其它模式,其中最常用的模式就是`--release`也就是生产发布的模式。 + +具体运行代码就不给了,留给大家作为一个小练习,建议亲自动手尝试下。 + +至此,Rust安装入门就已经结束,相信看到这里,你已经发现了本书与其它书的区别,其中最大的区别就是:**这本书就像优秀的国外课本一样,由浅入深的在带领大家学习Rust,而不是简单的复述一些语言的概念**,你可以回忆一下国内的线性代数等计算机教材,都是可以做为对照的反例(当然,不是说这些教材不优秀,只是这些教材需要优秀的老师支撑,而我们并没有)。 diff --git a/src/monitor/log.md b/src/monitor/log.md index 65c2dbdd..f34771f4 100644 --- a/src/monitor/log.md +++ b/src/monitor/log.md @@ -1 +1,9 @@ # 日志 + + +```rust + if cfg!(debug_assertions) { + eprintln!("debug: {:?} -> {:?}", + record, fields); + } +``` \ No newline at end of file diff --git a/src/monitor/observability b/src/monitor/observability.md similarity index 100% rename from src/monitor/observability rename to src/monitor/observability.md diff --git a/src/style-guide/clippy.md b/src/style-guide/clippy.md new file mode 100644 index 00000000..04994de1 --- /dev/null +++ b/src/style-guide/clippy.md @@ -0,0 +1 @@ +# Clippy