Merge pull request #448 from AllanDowney/patch-1

Update: unified format
pull/471/head
孙飞Sunface 3 years ago committed by GitHub
commit bb4b053b4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,7 +13,7 @@
一箩筐的理由~~ 让我们先从第二点讲起。 一箩筐的理由~~ 让我们先从第二点讲起。
#### 为外部类型实现外部特征 #### 为外部类型实现外部特征
在之前的章节中,我们有讲过如果在外部类型上实现外部特征必须使用 `newtype` 的方式,否则你就得遵循孤儿规则:要为类型 `A` 实现特征 `T`,那么 `A` 或者 `T` 必须至少有一个在当前的作用范围内。 在之前的章节中,我们有讲过如果在外部类型上实现外部特征必须使用 `newtype` 的方式,否则你就得遵循孤儿规则:要为类型 `A` 实现特征 `T`,那么 `A` 或者 `T` 必须至少有一个在当前的作用范围内。
例如,如果想使用 `println!("{}", v)` 的方式去格式化输出一个动态数组 `Vec`,以期给用户提供更加清晰可读的内容,那么就需要为 `Vec` 实现 `Display` 特征,但是这里有一个问题: `Vec` 类型定义在标准库中,`Display` 亦然,这时就可以祭出大杀器 `newtype` 来解决: 例如,如果想使用 `println!("{}", v)` 的方式去格式化输出一个动态数组 `Vec`,以期给用户提供更加清晰可读的内容,那么就需要为 `Vec` 实现 `Display` 特征,但是这里有一个问题: `Vec` 类型定义在标准库中,`Display` 亦然,这时就可以祭出大杀器 `newtype` 来解决:
```rust ```rust
@ -36,7 +36,7 @@ fn main() {
如上所示,使用元组结构体语法 `struct Wrapper(Vec<String>)` 创建了一个 `newtype` Wrapper然后为它实现 `Display` 特征,最终实现了对 `Vec` 动态数组的格式化输出。 如上所示,使用元组结构体语法 `struct Wrapper(Vec<String>)` 创建了一个 `newtype` Wrapper然后为它实现 `Display` 特征,最终实现了对 `Vec` 动态数组的格式化输出。
#### 更好的可读性及类型异化 #### 更好的可读性及类型异化
首先,更好的可读性不等于更少的代码(如果你学过Scala相信会深有体会),其次下面的例子只是一个示例,未必能体现出更好的可读性: 首先,更好的可读性不等于更少的代码如果你学过Scala相信会深有体会,其次下面的例子只是一个示例,未必能体现出更好的可读性:
```rust ```rust
use std::ops::Add; use std::ops::Add;
use std::fmt; use std::fmt;
@ -238,7 +238,7 @@ fn generic<T>(t: T) {
} }
``` ```
该函数很简单就一个泛型参数T那么如保证 `T` 是固定大小的类型?仔细回想下,貌似在之前的课程章节中,我们也没有做过任何事情去做相关的限制,那 `T` 怎么就成了固定大小的类型了?奥秘在于编译器自动帮我们加上了 `Sized` 特征约束: 该函数很简单就一个泛型参数T那么如保证 `T` 是固定大小的类型?仔细回想下,貌似在之前的课程章节中,我们也没有做过任何事情去做相关的限制,那 `T` 怎么就成了固定大小的类型了?奥秘在于编译器自动帮我们加上了 `Sized` 特征约束:
```rust ```rust
fn generic<T: Sized>(t: T) { fn generic<T: Sized>(t: T) {
// --snip-- // --snip--

@ -10,7 +10,7 @@
与栈相反,堆上内存则是从低位地址向上增长,**堆内存通常只受物理内存限制**,而且通常是不连续的,因此从性能的角度看,栈往往比对堆更高。 与栈相反,堆上内存则是从低位地址向上增长,**堆内存通常只受物理内存限制**,而且通常是不连续的,因此从性能的角度看,栈往往比对堆更高。
相比其它语言Rust 堆上对象还有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制:当赋值时,发生的是所有权的转移(只需浅拷贝栈上的引用或智能指针即可),例如以下代码: 相比其它语言Rust 堆上对象还有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制:当赋值时,发生的是所有权的转移(只需浅拷贝栈上的引用或智能指针即可),例如以下代码:
```rust ```rust
fn main() { fn main() {
let b = foo("world"); let b = foo("world");
@ -60,7 +60,7 @@ fn main() {
- `println!` 可以正常打印出 `a` 的值,是因为它隐式地调用了 `Deref` 对智能指针 `a` 进行了解引用 - `println!` 可以正常打印出 `a` 的值,是因为它隐式地调用了 `Deref` 对智能指针 `a` 进行了解引用
- 最后一行代码 ` let b = a + 1` 报错,是因为在表达式中,我们无法自动隐式地执行 `Deref` 解引用操作,你需要使用 `*` 操作符 `let b = *a + 1`,来显式的进行解引用 - 最后一行代码 ` let b = a + 1` 报错,是因为在表达式中,我们无法自动隐式地执行 `Deref` 解引用操作,你需要使用 `*` 操作符 `let b = *a + 1`,来显式的进行解引用
- `a` 持有的智能指针将在作用结束(`main` 函数结束)时,被释放掉,这是因为 `Box<T>` 实现了 `Drop` 特征 - `a` 持有的智能指针将在作用域结束(`main` 函数结束)时,被释放掉,这是因为 `Box<T>` 实现了 `Drop` 特征
以上的例子在实际代码中其实很少会存在因为将一个简单的值分配到堆上并没有太大的意义。将其分配在栈上由于寄存器、CPU 缓存的原因,它的性能将更好,而且代码可读性也更好。 以上的例子在实际代码中其实很少会存在因为将一个简单的值分配到堆上并没有太大的意义。将其分配在栈上由于寄存器、CPU 缓存的原因,它的性能将更好,而且代码可读性也更好。
@ -246,7 +246,7 @@ fn gen_static_str() -> &'static str{
那么我说一个简单的场景,**你需要一个在运行期初始化的值,但是可以全局有效,也就是和整个程序活得一样久**,那么久可以使用 `Box::leak`,例如有一个存储配置的结构体实例,它是在运行期动态插入内容,那么就可以将其转为全局有效,虽然 `Rc/Arc` 也可以实现此功能,但是 `Box::leak` 是性能最高的。 那么我说一个简单的场景,**你需要一个在运行期初始化的值,但是可以全局有效,也就是和整个程序活得一样久**,那么久可以使用 `Box::leak`,例如有一个存储配置的结构体实例,它是在运行期动态插入内容,那么就可以将其转为全局有效,虽然 `Rc/Arc` 也可以实现此功能,但是 `Box::leak` 是性能最高的。
## 总结 ## 总结
`Box` 背后是调用 `jemalloc` 来做内存管理,所以堆上的空间无需我们的手动管理。与此类似,带 GC 的语言中的对象也是借助于 `Box` 概念来实现的,一切皆对象 = 一切皆 Box 只不过我们无需自己去 `Box` 罢了。 `Box` 背后是调用 `jemalloc` 来做内存管理,所以堆上的空间无需我们的手动管理。与此类似,带 GC 的语言中的对象也是借助于 `Box` 概念来实现的,**一切皆对象 = 一切皆 Box** 只不过我们无需自己去 `Box` 罢了。
其实很多时候编译器的鞭笞可以助我们更快的成长例如所有权规则里的借用、move、生命周期就是编译器在教我们做人哦不是是教我们深刻理解堆栈、内存布局、作用域等等你在其它 GC 语言无需去关注的东西。刚开始是很痛苦,但是一旦熟悉了这套规则,写代码的效率和代码本身的质量将飞速上升,直到你可以用 Java 开发的效率写出 Java 代码不可企及的性能和安全性,最终 Rust 语言所谓的开发效率低、心智负担高,对你来说终究不是个事。 其实很多时候编译器的鞭笞可以助我们更快的成长例如所有权规则里的借用、move、生命周期就是编译器在教我们做人哦不是是教我们深刻理解堆栈、内存布局、作用域等等你在其它 GC 语言无需去关注的东西。刚开始是很痛苦,但是一旦熟悉了这套规则,写代码的效率和代码本身的质量将飞速上升,直到你可以用 Java 开发的效率写出 Java 代码不可企及的性能和安全性,最终 Rust 语言所谓的开发效率低、心智负担高,对你来说终究不是个事。

@ -48,7 +48,7 @@ eprintln!("Error: Could not complete task")
它们仅应该被用于输出错误信息和进度信息,其它场景都应该使用 `print!` 系列。 它们仅应该被用于输出错误信息和进度信息,其它场景都应该使用 `print!` 系列。
## {} 与 {:?} ## {} 与 {:?}
与其它语言常用的 `%d``%s` 不同Rust 特立独行地选择了 `{}` 作为格式化占位符(说到这个有点想吐槽下Rust 中自创的概念其实还挺多的,真不知道该夸奖还是该吐槽-,-),事实证明,这种选择非常正确,它帮助用户减少了很多使用成本,你无需再为特定的类型选择特定的占位符,统一用 `{}` 来替代即可,剩下的类型推导等细节只要交给 Rust 去做。 与其它语言常用的 `%d``%s` 不同Rust 特立独行地选择了 `{}` 作为格式化占位符说到这个有点想吐槽下Rust 中自创的概念其实还挺多的,真不知道该夸奖还是该吐槽-,-,事实证明,这种选择非常正确,它帮助用户减少了很多使用成本,你无需再为特定的类型选择特定的占位符,统一用 `{}` 来替代即可,剩下的类型推导等细节只要交给 Rust 去做。
`{}` 类似,`{:?}` 也是占位符: `{}` 类似,`{:?}` 也是占位符:
@ -200,6 +200,16 @@ fn main() {
println!("{abc} {1}", abc = "def", 2); println!("{abc} {1}", abc = "def", 2);
``` ```
```rust
error: positional arguments cannot follow named arguments
--> src/main.rs:4:36
|
4 | println!("{abc} {1}", abc = "def", 2);
| ----- ^ positional arguments must be before named arguments
| |
| named argument
```
## 格式化参数 ## 格式化参数
格式化输出,意味着对输出格式会有更多的要求,例如只输出浮点数的小数点后两位: 格式化输出,意味着对输出格式会有更多的要求,例如只输出浮点数的小数点后两位:
```rust ```rust
@ -283,7 +293,7 @@ fn main() {
println!("{:+.2}", v); println!("{:+.2}", v);
// 不带小数 => 3 // 不带小数 => 3
println!("{:.0}", v); println!("{:.0}", v);
// 通过参数来设定精度 => 3.1416,相当于{:.4} // 通过参数来设定精度 => 3.1416相当于{:.4}
println!("{:.1$}", v, 4); println!("{:.1$}", v, 4);
let s = "hi我是Sunface孙飞"; let s = "hi我是Sunface孙飞";
@ -381,8 +391,8 @@ for (name, score) in get_scores() {
println!("{name}: {score:width$.precision$}"); println!("{name}: {score:width$.precision$}");
} }
``` ```
但也有局限,它只能捕获普通的变量,对于更复杂的类型(例如表达式),可以先将它赋值给一个变量或使用以前的`name = expression`形式的格式化参数。 但也有局限,它只能捕获普通的变量,对于更复杂的类型(例如表达式),可以先将它赋值给一个变量或使用以前的 `name = expression` 形式的格式化参数。
目前除了`panic!`外,其它接收格式化参数的宏,都可以使用新的特性。对于`panic!` 而言,如果还在使用`Rust2015`或`2018`大版本 ,那`panic!("{ident}")`依然会被当成 正常的字符串来处理,同时编译器会给予`warn`提示。而对于`2021版本`,则可以正常使用: 目前除了 `panic!` 外,其它接收格式化参数的宏,都可以使用新的特性。对于 `panic!` 而言,如果还在使用 `2015版本``2018版本`,那 `panic!("{ident}")` 依然会被当成 正常的字符串来处理,同时编译器会给予 `warn` 提示。而对于 `2021版本` ,则可以正常使用:
```rust ```rust
fn get_person() -> String { fn get_person() -> String {
String::from("sunface") String::from("sunface")

Loading…
Cancel
Save