diff --git a/contents/SUMMARY.md b/contents/SUMMARY.md index 913e1bcc..8eed03d6 100644 --- a/contents/SUMMARY.md +++ b/contents/SUMMARY.md @@ -151,7 +151,7 @@ - [易混淆概念解析](confonding/intro.md) - [切片和切片引用](confonding/slice.md) - - [String、&str 和 str](confonding/string.md) + - [原生指针、引用和智能指针 todo](confonding/pointer.md) - [作用域、生命周期和 NLL todo](confonding/lifetime.md) - [move、Copy和Clone todo](confonding/move-copy.md) diff --git a/contents/advance/macro.md b/contents/advance/macro.md index ff4020ce..3187b73a 100644 --- a/contents/advance/macro.md +++ b/contents/advance/macro.md @@ -158,7 +158,7 @@ let v = { 由于绝大多数 Rust 开发者都是宏的用户而不是编写者,因此在这里我们不会对 `macro_rules` 进行更深入的学习,如果大家感兴趣,可以看看这本书 [ “The Little Book of Rust Macros”](https://veykril.github.io/tlborm/)。 ## 用过程宏为属性标记生成代码 -第二种常用的宏就是[*过程宏*](https://doc.rust-lang.org/reference/procedural-macros.html) ( *procedural macros* ),从形式上来看,过程宏跟函数较为相像,但过程宏是使用源代码作为输入参数,基于代码进行一系列操作后,再输出一段全新的代码。**注意,过程宏输出的代码并不会替换之前的代码,这一点与声明宏有很大的不同!** +第二种常用的宏就是[*过程宏*](https://doc.rust-lang.org/reference/procedural-macros.html) ( *procedural macros* ),从形式上来看,过程宏跟函数较为相像,但过程宏是使用源代码作为输入参数,基于代码进行一系列操作后,再输出一段全新的代码。**注意,过程宏中的derive宏输出的代码并不会替换之前的代码,这一点与声明宏有很大的不同!** 至于前文提到的过程宏的三种类型(自定义 `derive`、属性宏、函数宏),它们的工作方式都是类似的。 @@ -462,4 +462,4 @@ Rust 中的宏主要分为两大类:声明宏和过程宏。 虽然 Rust 中的宏很强大,但是它并不应该成为我们的常规武器,原因是它会影响 Rust 代码的可读性和可维护性,我相信没有几个人愿意去维护别人写的宏 :) -因此,大家应该熟悉宏的使用场景,但是不要滥用,当你真的需要时,再回来查看本章了解实现细节,这才是最完美的使用方式。 \ No newline at end of file +因此,大家应该熟悉宏的使用场景,但是不要滥用,当你真的需要时,再回来查看本章了解实现细节,这才是最完美的使用方式。 diff --git a/contents/basic/formatted-output.md b/contents/basic/formatted-output.md index 0d1d5276..734fe140 100644 --- a/contents/basic/formatted-output.md +++ b/contents/basic/formatted-output.md @@ -350,6 +350,54 @@ fn main() { } ``` +## 在格式化字符串时捕获环境中的值(Rust1.58新增) + +在以前,想要输出一个函数的返回值,你需要这么做: +```rust +fn get_person() -> String { + String::from("sunface") +} +fn main() { + let p = get_person(); + println!("Hello, {}!", p); // implicit position + println!("Hello, {0}!", p); // explicit index + println!("Hello, {person}!", person = p); +} +``` +问题倒也不大,但是一旦格式化字符串长了后,就会非常冗余,而在1.58后,我们可以这么写: +```rust +fn get_person() -> String { + String::from("sunface") +} +fn main() { + let person = get_person(); + println!("Hello, {person}!"); +} +``` +是不是清晰、简洁了很多?甚至还可以将环境中的值用于格式化参数: +```rust +let (width, precision) = get_format(); +for (name, score) in get_scores() { + println!("{name}: {score:width$.precision$}"); +} +``` +但也有局限,它只能捕获普通的变量,对于更复杂的类型(例如表达式),可以先将它赋值给一个变量或使用以前的`name = expression`形式的格式化参数。 +目前除了`panic!`外,其它接收格式化参数的宏,都可以使用新的特性。对于`panic!` 而言,如果还在使用`Rust2015`或`2018`大版本 ,那`panic!("{ident}")`依然会被当成 正常的字符串来处理,同时编译器会给予`warn`提示。而对于`2021版本`,则可以正常使用: +```rust +fn get_person() -> String { + String::from("sunface") +} +fn main() { + let person = get_person(); + panic!("Hello, {person}!"); +} +``` + +输出: +```console +thread 'main' panicked at 'Hello, sunface!', src/main.rs:6:5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` ## 总结 把这些格式化都牢记在脑中是不太现实的,也没必要,我们要做的就是知道 Rust 支持相应的格式化输出,在需要之时,读者再来查阅本文即可。 diff --git a/contents/confonding/slice.md b/contents/confonding/slice.md index c6c464a1..ca08af67 100644 --- a/contents/confonding/slice.md +++ b/contents/confonding/slice.md @@ -1,7 +1,7 @@ # 切片和切片引用 关于 `str` / `&str`,`[u8]` / `&[u8]` 区别,你能清晰的说出来嘛?如果答案是 No ,那就跟随我一起来看看切片和切片引用到底有何区别吧。 -> 在继续之前,参见[此处](https://course.rs/basic/compound-type/string-slice.html#切片slice)了解何为切片 +> 在继续之前,查看[这里](https://course.rs/basic/compound-type/string-slice.html#切片slice)了解何为切片 切片允许我们引用集合中部分连续的元素序列,而不是引用整个集合。例如,字符串切片就是一个子字符串,数组切片就是一个子数组。 @@ -24,11 +24,24 @@ error[E0277]: the size for values of type `str` cannot be known at compilation t 也就是说,我们无法直接使用 `str`,而对于 `[u8]` 也是类似的,大家可以自己动手试试。 -总之,我们可以总结出一个结论:**在 Rust 中,所有的切片都是动态类型,它们都无法直接被使用**。 +总之,我们可以总结出一个结论:**在 Rust 中,所有的切片都是动态大小类型,它们都无法直接被使用**。 + +#### 为何切片是动态大小类型 +原因在于底层的切片长度是可以动态变化的,而编译器无法在编译期得知它的具体的长度,因此该类型无法被分配在栈上,只能分配在堆上。 + +#### 为何切片只能通过引用来使用 +既然切片只能分配到堆上,我们就无法直接使用它,大家可以想想,所有分配在堆上的数据,是不是都是通过一个在栈上的引用来访问的?切片也不例外。 + +#### 为何切片引用可以存储在栈上 +切片引用是一个宽指针,存储在栈上,指向了堆上的切片数据,该引用包含了切片的起始位置和长度,而且最重要的是,类似于指针,引用的大小是固定的(起始位置和长度都是整形),因此它才可以存储在栈上。 + +#### 有没有可以存储在栈上的 +有,使用固定长度的数组: `let a: [i8;4] = [1,2,3,4];`,注意看,数组的类型与切片是不同的,前者的类型带有长度:`[i8;4]`,而后者仅仅是 `[i8]`。 -那么问题来了,该如何使用切片呢? ## 切片引用 +那么问题来了,该如何使用切片呢? + 何以解忧,唯有引用。由于引用类型的大小在编译期是已知的,因此在 Rust 中,如果要使用切片,就必须要使用它的引用。 `str` 切片的引用类型是 `&str`,而 `[i32]` 的引用类型是 `&[i32]`,相信聪明的读者已经看出来了,`&str` 和 `&[i32]` 都是我们非常常用的类型,例如: @@ -46,7 +59,7 @@ let s3: &[i32] = &arr[1..3]; ## 总结 我们常常说使用切片,实际上我们在用的是切片的引用,我们也在频繁说使用字符串,实际上我们在使用的也是字符串切片的引用。 -总之,切片在 Rust 中是动态类型 DST,是无法被我们直接使用的,而我们在使用的都是且切片的引用。 +总之,切片在 Rust 中是动态类型 DST,是无法被我们直接使用的,而我们在使用的都是切片的引用。 | 切片 | 切片引用| | --- | --- | diff --git a/contents/profiling/performance/intro.md b/contents/profiling/performance/intro.md index f2254268..f9cb9ea5 100644 --- a/contents/profiling/performance/intro.md +++ b/contents/profiling/performance/intro.md @@ -1,5 +1,7 @@ # performance +https://nnethercote.github.io/perf-book/profiling.html + ## How do I profile a Rust web application in production? https://www.reddit.com/r/rust/comments/rupcux/how_do_i_profile_a_rust_web_application_in/