Merge pull request #123 from 1132719438/main

Some fixes in collections, method and result-error chapter
pull/127/head
Sunface 3 years ago committed by GitHub
commit 555aee8cc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,8 @@ my_gems.insert("河边捡的误以为是宝石的破石头", 18);
所有的集合类型都是动态的,意味着它们没有固定的内存大小,因此它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。同时,跟其它集合类型一致,`HashMap`也是内聚性的, 即所有的`K`必须拥有同样的类型,`V`也是如此。 所有的集合类型都是动态的,意味着它们没有固定的内存大小,因此它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。同时,跟其它集合类型一致,`HashMap`也是内聚性的, 即所有的`K`必须拥有同样的类型,`V`也是如此。
> 跟Vec一样如果预先知道要存储的KV对个数可以使用`HashMap::with_capacity(capacity)`创建指定大小的HashMap避免频繁的内存分配和拷贝提升性能
#### 使用迭代器和collect方法创建 #### 使用迭代器和collect方法创建
在实际使用中,不是所有的场景都能`new`一个哈希表后,然后悠哉悠哉的依次插入对应的键值对, 而是可能会从另外一个数据结构中,获取到对应的数据,最终生成`HashMap`. 在实际使用中,不是所有的场景都能`new`一个哈希表后,然后悠哉悠哉的依次插入对应的键值对, 而是可能会从另外一个数据结构中,获取到对应的数据,最终生成`HashMap`.
@ -170,7 +172,7 @@ let score: Option<&i32> = scores.get(&team_name);
上面有几点需要注意: 上面有几点需要注意:
- `get`方法返回一个`Option<&i32>`类型:当查询不到时,会返回一个`None`,查询到时返回`Some(&i32)` - `get`方法返回一个`Option<&i32>`类型:当查询不到时,会返回一个`None`,查询到时返回`Some(&i32)`
- `&32`是对`HashMap`中值的借用,如果不使用借用,可能会发生所有权的转移 - `&i32`是对`HashMap`中值的借用,如果不使用借用,可能会发生所有权的转移
还可以通过循环的方式依次遍历`KV`对: 还可以通过循环的方式依次遍历`KV`对:
```rust ```rust
@ -254,7 +256,7 @@ println!("{:?}", map);
好了理解完这个再来设想一点若一个复杂点的类型作为Key那怎么在底层对它进行存储怎么使用它进行查询和比较 是不是很棘手?好在我们有哈希函数:通过它把`Key`计算后映射为哈希值,然后使用该哈希值来进行存储、查询、比较等操作。 好了理解完这个再来设想一点若一个复杂点的类型作为Key那怎么在底层对它进行存储怎么使用它进行查询和比较 是不是很棘手?好在我们有哈希函数:通过它把`Key`计算后映射为哈希值,然后使用该哈希值来进行存储、查询、比较等操作。
但是问题又来了,如何保证不同`Key`通过哈希后的两个值不会相同?如果相同,那意味着我们使用不同的·,却查到了同一个结果,这种明显是错误的行为。 但是问题又来了,如何保证不同`Key`通过哈希后的两个值不会相同?如果相同,那意味着我们使用不同的Key,却查到了同一个结果,这种明显是错误的行为。
此时,就涉及到安全性跟性能的取舍了。 此时,就涉及到安全性跟性能的取舍了。
若要追求安全,尽可能减少冲突,同时防止拒绝服务(Denial of Service, DoS)攻击,就要使用密码学安全的哈希函数,`HashMap`就是使用了这样的哈希函数。反之若要追求性能,就需要使用没有那么安全的算法。 若要追求安全,尽可能减少冲突,同时防止拒绝服务(Denial of Service, DoS)攻击,就要使用密码学安全的哈希函数,`HashMap`就是使用了这样的哈希函数。反之若要追求性能,就需要使用没有那么安全的算法。

@ -8,7 +8,7 @@
紧接着,第二个集合在全场的嘘声和羡慕眼光中闪亮登场,只见里面的元素排成一对一对的,彼此都手牵着手,非对方莫属,这种情深深雨蒙蒙的样子真是...挺欠扁的。 它就是`HashMap`类型,该类型允许你在里面存储`KV`对,每一个`K`都有唯一的`V`与之配对。 紧接着,第二个集合在全场的嘘声和羡慕眼光中闪亮登场,只见里面的元素排成一对一对的,彼此都手牵着手,非对方莫属,这种情深深雨蒙蒙的样子真是...挺欠扁的。 它就是`HashMap`类型,该类型允许你在里面存储`KV`对,每一个`K`都有唯一的`V`与之配对。
最后,请用热的掌声迎接我们的`String`集合,哦,抱歉,`String`集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](../compound-type/string-slice)找它. 最后,请用热的掌声迎接我们的`String`集合,哦,抱歉,`String`集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](../compound-type/string-slice)找它.
言归正传,本章所讲的`Vector`、`HashMap`再加上之前的`String`类型,是标准库中最最常用的集合类型,可以说,几乎任何一段代码中都可以找到它们的身影,那么先来看看`Vector`. 言归正传,本章所讲的`Vector`、`HashMap`再加上之前的`String`类型,是标准库中最最常用的集合类型,可以说,几乎任何一段代码中都可以找到它们的身影,那么先来看看`Vector`.

@ -21,7 +21,9 @@ let mut v = Vec::new();
v.push(1); v.push(1);
``` ```
此时,`v`就无需手动声明类型,因为编译器通过`v.push(1)`,推测出`v`中的元素类型是`i32`,因此推导出`v`的类型是`Vec<id3>`. 此时,`v`就无需手动声明类型,因为编译器通过`v.push(1)`,推测出`v`中的元素类型是`i32`,因此推导出`v`的类型是`Vec<i32>`.
> 如果预先知道要存储的元素个数,可以使用`Vec::with_capacity(capacity)`创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能
#### vec![] #### vec![]
还可以使用宏`vec!`来创建数组,与`Vec::new`有所不同,前者能在创建同时给予初始化值: 还可以使用宏`vec!`来创建数组,与`Vec::new`有所不同,前者能在创建同时给予初始化值:
@ -38,7 +40,7 @@ let mut v = Vec::new();
v.push(1); v.push(1);
``` ```
与其它类型一样,必须将`v`声明为`mut`后,才能进行修改,. 与其它类型一样,必须将`v`声明为`mut`后,才能进行修改
## Vector与其元素共存亡 ## Vector与其元素共存亡
@ -192,7 +194,7 @@ impl IpAddr for V6 {
fn main() { fn main() {
let v: Vec<Box<dyn IpAddr>> = vec![ let v: Vec<Box<dyn IpAddr>> = vec![
Box::new(V4("127.0.0.1".to_string())), Box::new(V4("127.0.0.1".to_string())),
Box::new(V6("127.0.0.1".to_string())), Box::new(V6("::1".to_string())),
]; ];
for ip in v { for ip in v {

@ -159,7 +159,7 @@ error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
#### 点操作符 #### 点操作符
方法调用的点操作符看起来简单,实际上非常不简单,它在调用时,会发生很多魔法般的类型转换,例如:自动引用、自动解引用,强制类型转换直到类型能匹配等。 方法调用的点操作符看起来简单,实际上非常不简单,它在调用时,会发生很多魔法般的类型转换,例如:自动引用、自动解引用,强制类型转换直到类型能匹配等。
假设有一个方法`foo`,它有一个接收器(接收器就是`self`、`&sef`、`&mut self`参数)。如果调用`value.foo()`,编译器在调用`foo`之前,需要决定到底使用哪个`Self`类型来调用。现在假设`value`拥有类型`T`. 假设有一个方法`foo`,它有一个接收器(接收器就是`self`、`&self`、`&mut self`参数)。如果调用`value.foo()`,编译器在调用`foo`之前,需要决定到底使用哪个`Self`类型来调用。现在假设`value`拥有类型`T`.
再进一步,我们使用[完全限定语法](https://course.rs/basic/trait/advance-trait.html#完全限定语法)来进行准确的函数调用: 再进一步,我们使用[完全限定语法](https://course.rs/basic/trait/advance-trait.html#完全限定语法)来进行准确的函数调用:
1. 首先,编译器检查它是否可以直接调用`T::foo(value)`, 称之为**值方法调用** 1. 首先,编译器检查它是否可以直接调用`T::foo(value)`, 称之为**值方法调用**
@ -267,6 +267,6 @@ impl<T> Clone for Container<T> {
你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。`mem::transmute_copy<T, U>`才是真正的深渊,它比之前的还要更加危险和不安全。它从`T`类型中拷贝出`U`类型所需的字节数,然后转换成`U`。`mem::transmute`尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过`U`的尺寸若是比`T`大,会是一个未定义行为。 你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。`mem::transmute_copy<T, U>`才是真正的深渊,它比之前的还要更加危险和不安全。它从`T`类型中拷贝出`U`类型所需的字节数,然后转换成`U`。`mem::transmute`尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过`U`的尺寸若是比`T`大,会是一个未定义行为。
当然,你也可以通过原生指针转换获得`unions`(todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和`unions`也不是魔法,无法逃避上面说的规则。 当然,你也可以通过原生指针转换`unions`(todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和`unions`也不是魔法,无法逃避上面说的规则。

@ -4,7 +4,7 @@
社会演变至今,这种思想依然没变,甚至来到计算中的微观世界,也是如此。及时、准确的获知系统在发生什么,是程序设计的重中之重。因此能够准确的分辨函数返回值是正确的还是错误的、以及在发生错误时该怎么快速处理,成了程序设计语言的必备功能。 社会演变至今,这种思想依然没变,甚至来到计算中的微观世界,也是如此。及时、准确的获知系统在发生什么,是程序设计的重中之重。因此能够准确的分辨函数返回值是正确的还是错误的、以及在发生错误时该怎么快速处理,成了程序设计语言的必备功能。
Go语言为人诟病的其中一点就是`if err != nil {}`的大量使用缺乏一些程序设计的美感不过我倒是觉得这种简单的方式也有其好处就是阅读代码时的流畅感很强你不需要过多的思考各种语法是什么意思。与Go语言不同Rust博采众家之长整出了颇具自身色彩的返回值和错误处理体系本章我们就从高屋建瓴的角度来学习更加深入的讲解见[此章](../errors/intro.md). Go语言为人诟病的其中一点就是`if err != nil {}`的大量使用缺乏一些程序设计的美感不过我倒是觉得这种简单的方式也有其好处就是阅读代码时的流畅感很强你不需要过多的思考各种语法是什么意思。与Go语言不同Rust博采众家之长整出了颇具自身色彩的返回值和错误处理体系本章我们就从高屋建瓴的角度来学习更加深入的讲解见[此章](../../errors/intro.md).
## Rust的错误哲学 ## Rust的错误哲学
错误对于软件来说是不可避免的因此一门优秀的编程语言必须有其完整的错误处理哲学。在很多情况下Rust需要你承认自己的代码可能会出错并提前采取行动来处理这些错误。 错误对于软件来说是不可避免的因此一门优秀的编程语言必须有其完整的错误处理哲学。在很多情况下Rust需要你承认自己的代码可能会出错并提前采取行动来处理这些错误。
@ -13,4 +13,4 @@ Rust中的错误主要分为两类
- **可恢复错误**, 通常用于从系统全局角度来看可以接受的错误,例如处理用户的访问、操作等错误,这些错误只会影响某个用户自身的操作进程,而不会对系统的全局稳定性产生影响 - **可恢复错误**, 通常用于从系统全局角度来看可以接受的错误,例如处理用户的访问、操作等错误,这些错误只会影响某个用户自身的操作进程,而不会对系统的全局稳定性产生影响
- **不可恢复错误**,刚好相反,该错误通常是全局性或者系统性的错误,例如数组越界访问,系统启动时发生了影响启动流程的错误等等,这些错误的影响往往对于系统来说是致命的 - **不可恢复错误**,刚好相反,该错误通常是全局性或者系统性的错误,例如数组越界访问,系统启动时发生了影响启动流程的错误等等,这些错误的影响往往对于系统来说是致命的
很多编程语言并不会区分这些错误而是直接采用异常的方式去处理。Rust没有异常但是Rust也有自己的卧龙凤雏`Result<T,e>`用于可恢复错误,`panic!`用于不可恢复错误。 很多编程语言并不会区分这些错误而是直接采用异常的方式去处理。Rust没有异常但是Rust也有自己的卧龙凤雏`Result<T,E>`用于可恢复错误,`panic!`用于不可恢复错误。

@ -115,7 +115,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
因此`unwrap`简而言之:成功则返回值,失败则`panic`, 总之不进行任何错误处理。 因此`unwrap`简而言之:成功则返回值,失败则`panic`, 总之不进行任何错误处理。
#### 示例、原型、测试 #### 示例、原型、测试
这些场景,需要快速的搭建代码,错误处理反而会拖慢实现速度,也不是特别有必要,因此通过`unrap`、`expect`等方法来处理是最快的。 这些场景,需要快速的搭建代码,错误处理反而会拖慢实现速度,也不是特别有必要,因此通过`unwrap`、`expect`等方法来处理是最快的。
同时,当我们准备做错误处理时,全局搜索这些方法,也可以不遗漏的进行替换。 同时,当我们准备做错误处理时,全局搜索这些方法,也可以不遗漏的进行替换。
@ -138,7 +138,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
当错误预期会出现时返回一个错误较为合适例如解析器接收到格式错误的数据HTTP请求接收到错误的参数甚至该请求内的任何错误(不会导致整个程序有问题,只影响该此请求)。 **因为错误是可预期的,因此也是可以处理的**。 当错误预期会出现时返回一个错误较为合适例如解析器接收到格式错误的数据HTTP请求接收到错误的参数甚至该请求内的任何错误(不会导致整个程序有问题,只影响该此请求)。 **因为错误是可预期的,因此也是可以处理的**。
当启动时某个流程发生了错误,导致了后续代码的允许造成影响,那么就应该使用`panic`,而不是处理错误后,继续运行,当然你可以通过重试的方式来继续。 当启动时某个流程发生了错误,导致了后续代码的运行造成影响,那么就应该使用`panic`,而不是处理错误后,继续运行,当然你可以通过重试的方式来继续。
上面提到过,数组访问越界,就要`panic`的原因,这个就是属于内存安全的范畴,一旦内存访问不安全,那么我们无法保证自己的程序会怎么运行下去,也无法保证逻辑和数据的正确性。 上面提到过,数组访问越界,就要`panic`的原因,这个就是属于内存安全的范畴,一旦内存访问不安全,那么我们无法保证自己的程序会怎么运行下去,也无法保证逻辑和数据的正确性。

@ -24,7 +24,7 @@ fn main() {
> >
> 有几种常用的方式: > 有几种常用的方式:
> - 第一种是查询标准库或者三方库文档,搜索`File`,然后找到它的`open`方法,但是此处更推荐第二种方法: > - 第一种是查询标准库或者三方库文档,搜索`File`,然后找到它的`open`方法,但是此处更推荐第二种方法:
> - 在[Rust IDE](../../first-try/editor.md)章节,我们推荐了`VSCode` IED和`rust-analyze`插件,如果你成功安装的话,那么就可以在`VScode`中很方便的通过代码跳转的方式查看代码,同时`rust-analyze`插件还会对代码中的类型进行标注,非常方便好用! > - 在[Rust IDE](../../first-try/editor.md)章节,我们推荐了`VSCode` IED和`rust-analyzer`插件,如果你成功安装的话,那么就可以在`VScode`中很方便的通过代码跳转的方式查看代码,同时`rust-analyzer`插件还会对代码中的类型进行标注,非常方便好用!
> - 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你: > - 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你:
```rust ```rust
let f: u32 = File::open("hello.txt"); let f: u32 = File::open("hello.txt");
@ -314,7 +314,7 @@ fn main() -> Result<(), Box<dyn Error>> {
这样就能使用`?`提前返回了,同时我们又一次看到了`Box<dyn Error>`特征对象,因为`std::error:Error`是Rust中抽象层次最高的错误其它标准库中的错误都实现了该特征因此我们可以用该特征对象代表一切错误就算`main`函数中调用任何标准库函数发生错误,都可以通过`Box<dyn Error>`这个特征对象进行返回. 这样就能使用`?`提前返回了,同时我们又一次看到了`Box<dyn Error>`特征对象,因为`std::error:Error`是Rust中抽象层次最高的错误其它标准库中的错误都实现了该特征因此我们可以用该特征对象代表一切错误就算`main`函数中调用任何标准库函数发生错误,都可以通过`Box<dyn Error>`这个特征对象进行返回.
至于`main`函数可以有多种返回值,那是因为实现了[std::process::Termination]特征,目前为止该特征还没进入稳定版Rust中也许未来你可以为自己的类型实现该特征 至于`main`函数可以有多种返回值,那是因为实现了[std::process::Termination](https://doc.rust-lang.org/std/process/trait.Termination.html)特征,目前为止该特征还没进入稳定版Rust中也许未来你可以为自己的类型实现该特征
至此Rust的基础内容学习已经全部完成下面我们将学习Rust的高级进阶内容正式开启你的高手之路。 至此Rust的基础内容学习已经全部完成下面我们将学习Rust的高级进阶内容正式开启你的高手之路。

Loading…
Cancel
Save