diff --git a/book-contents/SUMMARY.md b/book-contents/SUMMARY.md index bd4774d7..329eae61 100644 --- a/book-contents/SUMMARY.md +++ b/book-contents/SUMMARY.md @@ -58,7 +58,7 @@ - [闭包closure](advance/functional-programing/closure.md) - [迭代器iterator](advance/functional-programing/iterator.md) - [深入类型之newtype和Sized](advance/custom-type.md) - - [格式化输出 todo](advance/formatted-output.md) + - [格式化输出](advance/formatted-output.md) - [文档注释 todo](advance/comment.md) - [包和模块 todo](advance/crate-module.md) - [智能指针 todo](advance/smart-pointer.md) @@ -167,6 +167,7 @@ - [Benchmark性能测试(todo)](performance/benchmark.md) - [减少Runtime check(todo)](performance/runtime-check.md) - [CPU缓存性能优化](performance/cpu-cache.md) + - [计算性能优化](performance/calculate.md) - [堆和栈](performance/heap-stack.md) - [常用性能测试工具](performance/tools.md) diff --git a/book-contents/advance/formatted-output.md b/book-contents/advance/formatted-output.md index 394a91d9..5ccac992 100644 --- a/book-contents/advance/formatted-output.md +++ b/book-contents/advance/formatted-output.md @@ -1 +1,348 @@ -# formatted-output.md \ No newline at end of file +# 格式化输出 +提到格式化输出,可能很多人立刻就想到`"{}"`,但是Rust能做到的远比这个多的多,本章节我们将深入讲解格式化输出的各个方面。 + +## 满分初印象 +先来一段代码,看看格式化输出的初印象: +```rust +println!("Hello"); // => "Hello" +println!("Hello, {}!", "world"); // => "Hello, world!" +println!("The number is {}", 1); // => "The number is 1" +println!("{:?}", (3, 4)); // => "(3, 4)" +println!("{value}", value=4); // => "4" +println!("{} {}", 1, 2); // => "1 2" +println!("{:04}", 42); // => "0042" with leading zeros +``` + +可以看到`println!`宏接受的是可变参数,第一个参数是一个字符串常量,它表示最终输出字符串的格式, 包含其中形如`{}`的符号是**占位符**, 会被`println!`后面的参数依次替换。 + +## `print!`, `println!`, `format!` +它们是Rust中用来格式化输出的三大金刚,用途如下: + +- `print!`, 将格式化文本输出到标准输出,不带换行符 +- `println!`, 同上,但是在行的末尾添加换行符 +- `format!`, 将格式化文本输出到`String`字符串 + +在实际项目中,最常用的是`println!`及`format!`,前者常用来调试输出,后者用来生成格式化的字符串: +```rust +fn main() { + let s = "hello"; + println!("{}, world",s); + let s1 = format!("{}, world", s); + print!("{}",s1); + print!("{}\n","!"); +} +``` + +其中, `s1`是通过`format!`生成的`String`字符串,最终输出如下: +```console +hello, wolrd +hello, world! +``` + +#### `eprint!`, `eprintln!` +除了三大金刚外,还有两大护法,使用方式跟`print!`,`println!`很像,但是它们输出到标准错误输出: +```rust +eprintln!("Error: Could not complete task") +``` + +它们仅应该被用于输出错误信息和进度信息,其它场景都应该使用`print!`系列。 + +## {}与{:?} +与其它语言常用的`%d`,`%s`不同,Rust特立独行的选择了`{}`作为格式化占位符(说到这个,有点想吐槽下,Rust中自创的概念其实还挺多的,真不知道该夸奖还是该吐槽- , -),事实证明,这种选择非常正确,它帮助用户减少了很多使用成本,你无需再为特定的类型选择特定的占位符,统一用`{}`来替代即可,剩下的类型推导等细节只要交给Rust去做。 + +与`{}`类似,`{:?}`也是占位符: + +- `{}`适用于实现了`std::fmt::Display`特征的类型,用来以更优雅、更友好的方式格式化文本,例如展示给用户 +- `{:?}`适用于实现了`std::fmt::Debug`特征的类型,用于调试场景 + +其实两者的选择很简单,当你在写代码需要调试时,使用`{:?}`,剩下的场景,选择`{}`。 + +#### `Debug`特征 +事实上,为了方便我们调试,大多数Rust类型都实现了`Debug`特征或者支持派生该特征: +```rust +#[derive(Debug)] +struct Person { + name: String, + age: u8 +} + +fn main() { + let i = 3.1415926; + let s = String::from("hello"); + let v = vec![1,2,3]; + let p = Person{name: "sunface".to_string(),age: 18}; + println!("{:?}, {:?}, {:?},{:?}",i,s,v,p); +} +``` + +对于数值、字符串、数组,可以直接使用`{:?}`进行输出,但是对于结构体,需要[派生`Debug`](../appendix/derive.md)特征后,才能进行输出,总之很简单. + + +#### `Display`特征 +与大部分类型实现了`Debug`不同,实现了`Display`特征的Rust类型并没有那么多,往往需要我们自定义想要的格式化方式: +```rust +let i = 3.1415926; +let s = String::from("hello"); +let v = vec![1,2,3]; +let p = Person{name: "sunface".to_string(),age: 18}; +println!("{}, {}, {},{}",i,s,v,p); +``` + +运行后可以看到`v`和`p`都无法通过编译,因为没有实现`Display`特征,但是你又不能像派生`Debug`一般派生`Display`,只能另寻他法: + +- 使用`{:?}`或`{:#?}` +- 为自定义类型实现`Display`特征 +- 使用`newtype`为外部类型实现`Display`特征 + +下面来一一看看这三种方式。 + +#### {:#?} +`{:#?}`与`{:?}`几乎一样,唯一的区别在于它能更优美的输出内容: +```console +// {:?} +[1, 2, 3],Person { name: "sunface", age: 18 } + +// {:#?} +[ + 1, + 2, + 3, +],Person { + name: "sunface", +} +``` + +因此对于`Display`不支持的类型,可以考虑使用`{:#?}`进行格式化,虽然理论上它更适合进行调试输出。 + +#### 为自定义类型实现`Display`特征 +如果你的类型是定义在当前作用域中的,那么可以为其实现`Display`特征,即可用于格式化输出: +```rust +struct Person { + name: String, + age: u8 +} + +use std::fmt; +impl fmt::Display for Person { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "大佬在上,请受我一拜,小弟姓名{},年芳{},家里无田又无车,生活苦哈哈",self.name,self.age) + } +} +fn main() { + let p = Person{name: "sunface".to_string(),age: 18}; + println!("{}", p); +} +``` + +如上所示,只要实现`Display`特征中的`fmt`方法,即可为自定义结构体`Person`添加自定义输出: +```console +大佬在上,请受我一拜,小弟姓名sunface,年芳18,家里无田又无车,生活苦哈哈 +``` + +#### 为外部类型实现`Display`特征 +在Rust中,无法直接为外部类型实现外部特征,但是可以使用[`newtype`](./custom-type.md#newtype)解决此问题: +```rust +struct Array(Vec); + +use std::fmt; +impl fmt::Display for Array { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "数组是:{:?}",self.0) + } +} +fn main() { + let arr = Array(vec![1, 2, 3]); + println!("{}", arr); +} +``` + +`Array`就是我们的`newtype`,它将想要格式化输出的`Vec`包裹在内,最后只要为`Arraw`实现`Display`特征,即可进行格式化输出: +```console +数组是:[1, 2, 3] +``` + +至此,关于`{}`与`{:?}`的内容已介绍完毕,下面让我们正式开始格式化输出的旅程。 + +## 指定位置参数 +除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如`{1}`,表示用第二个参数替换该占位符(索引从0开始): +```rust +fn main() { + println!("{}{}",1,2); // =>"12" + println!("{1}{0}",1,2); // =>"21" + // => Alice, this is Bob. Bob, this is Alice + println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); + println!("{1}{}{0}{}",1,2); // => 2112 +} +``` + +## 带名称的变量 +除了像上面那样指定位置外,我们还可以为参数指定名称: +```rust +fn main() { + println!("{argument}", argument = "test"); // => "test" + println!("{name} {}", 1, name = 2); // => "2 1" + println!("{a} {c} {b}", a = "a", b = 'b', c = 3); // => "a 3 b" +} +``` + +需要注意的是: **带名称的参数必须放在不带名称参数的后面**,例如下面代码将报错: +```rust +println!("{abc} {1}", abc = "def", 2); +``` + +## 格式化参数 +格式化输出,意味着对输出格式会有更多的要求,例如只输出浮点数的小数点后两位: +```rust +fn main() { + let v = 3.1415926; + // Display => 3.14 + println!("{:.2}",v); + // Debug => 3.14 + println!("{:.2?}",v); +} +``` + +上面代码只输出小数点后两位。同时我们还展示了`{}`和`{:?}`的用法,后面如无特殊区别,就只针对`{}`提供格式化参数说明。 + +接下来,让我们一起来看看Rust中有哪些格式化参数。 + +#### 宽度 +宽度用来指示输出目标的长度,如果长度不够,则进行填充和对齐: + +##### 字符串填充 +字符串格式化默认使用空格进行填充,并且进行左对齐. +```rust +fn main() { + //----------------------------------- + // 以下全部输出 "Hello x !" + // 为"x"后面填充空格,补齐宽度5 + println!("Hello {:5}!", "x"); + // 使用参数5来指定宽度 + println!("Hello {:1$}!", "x", 5); + // 使用x作为占位符输出内容,同时使用5作为宽度 + println!("Hello {1:0$}!", 5, "x"); + // 使用有名称的参数作为宽度 + println!("Hello {:width$}!", "x", width = 5); + //----------------------------------- + + // 使用参数5为参数x指定宽度,同时在结尾输出参数5 => Hello x !5 + println!("Hello {:1$}!{}", "x", 5); +} +``` + +##### 数字填充:符号和0 +数字格式化默认也是使用空格进行填充,但与字符串左对齐不同的是,数字是右对齐。 +```rust +fn main() { + // 宽度是5 => Hello 5! + println!("Hello {:5}!", 5); + // 显式的输出正号 => Hello +5! + println!("Hello {:+}!", 5); + // 宽度5,使用0进行填充 => Hello 00005! + println!("Hello {:05}!", 5); + // 负号也要占用一位宽度 => Hello -0005! + println!("Hello {:05}!", -5); +} +``` + +##### 对齐 +```rust +fn main() { + // 以下全部都会补齐5个字符的长度 + // 左对齐 => Hello x ! + println!("Hello {:<5}!","x"); + // 右对齐 => Hello x + println!("Hello {:>5}!","x"); + // 居中对齐 => Hello x ! + println!("Hello {:^5}!","x"); + + // 对齐并使用指定符号填充 => Hello x&&&&! + // 指定符号填充的前提条件是必须有对齐字符 + println!("Hello {:&<5}!", "x"); +} +``` + +#### 精度 +精度可以用于控制浮点数的精度或者字符串的长度 +```rust +fn main() { + let v = 3.1415926; + // 保留小数点后两位 => 3.14 + println!("{:.2}",v); + // 带符号保留小数点后两位 => +3.14 + println!("{:+.2}",v); + // 不带小数 => 3 + println!("{:.0}",v); + // 通过参数来设定精度 => 3.1416,相当于{:.4} + println!("{:.1$}", v, 4); + + let s = "hi我是Sunface孙飞"; + // 保留字符串前三个字符 => hi我 + println!("{:.3}", s); + // {:.*}接收两个参数,第一个是精度,第二个是被格式化的值 => Hello abc! + println!("Hello {:.*}!", 3, "abcdefg"); +} +``` + +#### 进制 +可以使用`#`号来控制数字的进制输出: + +- #b, 二进制 +- #o, 八进制 +- #x, 小写十六进制 +- #X, 大写十六进制 +- x, 不带前缀的小写十六进制 + +```rust +fn main() { + // 二进制 => 0b11011! + println!("{:#b}!", 27); + // 八进制 => 0o33! + println!("{:#o}!", 27); + // 十进制 => 27! + println!("{}!", 27); + // 小写十六进制 => 0x1b! + println!("{:#x}!", 27); + // 大写十六进制 => 0x1B! + println!("{:#X}!", 27); + + // 不带前缀的十六进制 => 1b! + println!("{:x}!", 27); + + // 使用0填充二进制,宽度为10 => 0b00011011! + println!("{:#010b}!", 27); +} +``` + +#### 指数 +```rust +fn main() { + println!("{:2e}", 1000000000); // => 1e9 + println!("{:2E}", 1000000000); // => 1E9 +} +``` + +#### 指针地址 +```rust +let v= vec![1,2,3]; +println!("{:p}",v.as_ptr()) // => 0x600002324050 +``` + +#### 转义 +有时需要输出`{`和`}`,但这两个字符是特殊字符,需要进行转义: +```rust +fn main() { + // {使用{转义,}使用} => Hello {} + println!("Hello {{}}"); + + // 下面代码会报错,因为占位符{}只有一个右括号},左括号被转义成字符串的内容 + // println!("{{ Hello }"); +} +``` + + +## 总结 +把这些格式化都牢记在脑中是不太现实的,也没必要,我们要做的就是知道Rust支持相应的格式化输出,在需要之时,读者再来查阅本文即可。 + +还是那句话,[<>](https://github.com/sunface/rust-course)不仅仅是Rust学习书籍,还是一本厚重的工具书! + diff --git a/book-contents/basic/match-pattern/match-if-let.md b/book-contents/basic/match-pattern/match-if-let.md index 80794e62..bcfb9fc6 100644 --- a/book-contents/basic/match-pattern/match-if-let.md +++ b/book-contents/basic/match-pattern/match-if-let.md @@ -1,5 +1,7 @@ # match和if let +在Rust中,模式匹配最常用的就是`match`和`if let`,本章节将对两者及相关的概念进行详尽介绍。 + 先来看一个关于`match`的简单例子: ```rust enum Direction { @@ -264,6 +266,39 @@ if let Some(3) = some_u8_value { 这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好: **当你只要匹配一个条件,且忽略其他条件时就用`if let`,否则都用match**. +## matches!宏 +Rust标准库中提供了一个非常实用的宏:`matches!`,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果`true` or `false`. + +例如,有一个动态数组,里面存有以下枚举: +```rust +enum MyEnum { + Foo, + Bar +} + +fn main() { + let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::F +} +``` + +现在如果想对`v`进行过滤,只保留类型是`MyEnum::Foo`的元素,你可能想这么写: +```rust +v.iter().filter(|x| x == MyEnum:::Foo); +``` + +但是,实际上这行代码会保存,因为你无法将`x`跟一个类型进行比较。好在,你可以使用`match`来完成,但是会导致代码更为啰嗦,是否有更简洁的方式?答案是使用`matches!`: +```rust +v.iter().filter(|x| matches!(x, MyEnum::Foo)); +``` + +很简单也很简洁,再来看看更多的例子: +```rust +let foo = 'f'; +assert!(matches!(foo, 'A'..='Z' | 'a'..='z')); + +let bar = Some(4); +assert!(matches!(bar, Some(x) if x > 2)); +``` ## 变量覆盖 无论是是`match`还是`if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的直: diff --git a/book-contents/basic/trait/generic.md b/book-contents/basic/trait/generic.md index 6b1fb4be..2594cd7a 100644 --- a/book-contents/basic/trait/generic.md +++ b/book-contents/basic/trait/generic.md @@ -351,6 +351,9 @@ fn main() { 在泛型参数之前,Rust完全不适合复杂矩阵的运算,自从有了const泛型,一切即将改变。 +## const fn +@todo + ## 泛型的性能 在Rust中泛型是零消耗的抽象,意味着你在使用泛型时,完全不用担心性能上的问题。 diff --git a/book-contents/compiler/speed-up.md b/book-contents/compiler/speed-up.md index 4bde5433..4ad8fdec 100644 --- a/book-contents/compiler/speed-up.md +++ b/book-contents/compiler/speed-up.md @@ -1 +1,3 @@ # 优化编译速度 + +https://www.reddit.com/r/rust/comments/rnkyc0/why_does_my_code_compile_faster_on_nightly/ \ No newline at end of file diff --git a/book-contents/macro/procedure-macro.md b/book-contents/macro/procedure-macro.md index ebb9bbc6..3a7292ba 100644 --- a/book-contents/macro/procedure-macro.md +++ b/book-contents/macro/procedure-macro.md @@ -1,2 +1,4 @@ -https://www.reddit.com/r/rust/comments/rjumsg/any_good_resources_for_learning_rust_macros/ \ No newline at end of file +https://www.reddit.com/r/rust/comments/rjumsg/any_good_resources_for_learning_rust_macros/ + +https://www.reddit.com/r/rust/comments/roaofg/procedural_macros_parsing_custom_syntax/ \ No newline at end of file diff --git a/book-contents/performance/calculate.md b/book-contents/performance/calculate.md new file mode 100644 index 00000000..03d4042c --- /dev/null +++ b/book-contents/performance/calculate.md @@ -0,0 +1,138 @@ +# 计算性能优化 + + +https://www.reddit.com/r/rust/comments/rn7ozz/find_perfect_number_comparison_go_java_rust/ + + +```go +package main + +import ( + "fmt" + "math" + "time" +) + +func main() { + n := 320000 + nums := make(map[int][]int) + start := time.Now() + calPerfs(n, nums) + fmt.Printf("runtime: %s\n", time.Since(start)) +} + +func calPerfs(n int, nums map[int][]int) { + for i := 1; i <= n; i++ { + d := divs(i) + if sum(d) == i { + nums[i] = all(d) + } + } +} + +func divs(num int) map[int]struct{} { + r := make(map[int]struct{}) + r[1] = struct{}{} + mid := int(math.Sqrt(float64(num))) + for i := 2; i <= mid; i++ { + if num%i == 0 { + r[i] = struct{}{} + r[num/i] = struct{}{} + } + } + return r +} + +func sum(ds map[int]struct{}) int { + var n int + for k := range ds { + n += k + } + return n +} + +func all(ds map[int]struct{}) []int { + var a []int + for k := range ds { + a = append(a, k) + } + return a +} +``` + +## 120ms + +```rust +use std::time::Instant; + +const N: usize = 320_000 ; + +fn is_perfect(n: usize) -> bool { + //println!("{:?}", n); + let mut sum = 1; + let end = (n as f64).sqrt() as usize; + for i in 2..end + 1{ + if n % i == 0 { + if i * i == n { + sum += i; + } + else { + sum += i + n / i; + } + } + } + sum == n +} + +fn find_perfs(n: usize) -> Vec { + let mut perfs:Vec = vec![]; + for i in 2..n + 1 { + if is_perfect(i) { + perfs.push(i) + } + } + perfs +} + +fn main() { + let start = Instant::now(); + let perfects = find_perfs(N); + println!("{:?}", start.elapsed()); + println!("{:?}, in {:?}", perfects, N); +} +``` + +## 90ms + +```rust +use { + std::{time::Instant}, +}; + +const N: usize = 320000; + +// Optimized, takes about 320ms on an Core i7 6700 @ 3.4GHz +fn cal_perfs2(n: usize) -> Vec { + (1..=n) + .into_iter() + .filter(|i| cal2(*i) == *i) + .collect::>() +} + +fn cal2(n: usize) -> usize { + (2..=(n as f64).sqrt() as usize) + .into_iter() + .filter_map(|i| if n % i == 0 { Some([i, n / i]) } else { None }) + .map(|a| a[0] + a[1]) + .sum::() + + 1 +} + + +fn main() { + let start = Instant::now(); + let perf2 = cal_perfs2(N); + println!("{:?}",perf2); + println!("Optimized: {:?}", start.elapsed()); +} +``` \ No newline at end of file diff --git a/book-contents/performance/runtime-check.md b/book-contents/performance/runtime-check.md index dc8de8a6..489d49f7 100644 --- a/book-contents/performance/runtime-check.md +++ b/book-contents/performance/runtime-check.md @@ -19,4 +19,11 @@ for item in collection { ``` 第一种方式是循环索引,然后通过索引下标去访问集合,第二种方式是直接循环迭代集合中的元素,优劣如下: -- **性能**:第一种使用方式中`collection[index]`的索引访问,会因为边界检查(bounds checking)导致运行时的性能损耗 - Rust会检查并确认`index`是落在集合内也就是合法的,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的` \ No newline at end of file +- **性能**:第一种使用方式中`collection[index]`的索引访问,会因为边界检查(bounds checking)导致运行时的性能损耗 - Rust会检查并确认`index`是落在集合内也就是合法的,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的` + +## Box::leak +https://www.reddit.com/r/rust/comments/rntx7s/why_use_boxleak/ + + +## bounds check +https://www.reddit.com/r/rust/comments/rnbubh/whats_the_big_deal_with_bounds_checking/ \ No newline at end of file diff --git a/book-contents/practice/best-pratice.md b/book-contents/practice/best-pratice.md index adb5dae5..5bf953c8 100644 --- a/book-contents/practice/best-pratice.md +++ b/book-contents/practice/best-pratice.md @@ -1,3 +1,5 @@ # 最佳实践 -https://www.reddit.com/r/rust/comments/rgjsbt/whats_your_top_rust_tip_crate_tool_other_for/ \ No newline at end of file +https://www.reddit.com/r/rust/comments/rgjsbt/whats_your_top_rust_tip_crate_tool_other_for/ + +https://www.reddit.com/r/rust/comments/rnmmqz/question_how_to_keep_code_dry_when_many_similar/ \ No newline at end of file diff --git a/writing-material/books.md b/writing-material/books.md index 54c424bd..30896994 100644 --- a/writing-material/books.md +++ b/writing-material/books.md @@ -21,4 +21,6 @@ 11. [rustc开发者之书](https://rustc-dev-guide.rust-lang.org/method-lookup.html) -12. [Rust Style](https://doc.rust-lang.org/1.6.0/style/README.html) \ No newline at end of file +12. [Rust Style](https://doc.rust-lang.org/1.6.0/style/README.html) + +13. [Learning Rust](https://learning-rust.github.io/docs/a1.why_rust.html) \ No newline at end of file diff --git a/writing-material/posts/println.md b/writing-material/posts/println.md deleted file mode 100644 index 0832a28f..00000000 --- a/writing-material/posts/println.md +++ /dev/null @@ -1,7 +0,0 @@ -一些关于println的技巧 - -## 打印对象地址 -```rust - let v= vec![1,2,3]; - println!("{:p}",v.as_ptr()) -``` \ No newline at end of file