diff --git a/contents/advance/custom-type.md b/contents/advance/custom-type.md index fff793fd..dbb06b1a 100644 --- a/contents/advance/custom-type.md +++ b/contents/advance/custom-type.md @@ -13,9 +13,9 @@ 一箩筐的理由~~ 让我们先从第二点讲起。 #### 为外部类型实现外部特征 -在之前的章节中,我们有讲过如果在外部类型上实现外部特征必须使用 `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 use std::fmt; @@ -36,7 +36,7 @@ fn main() { 如上所示,使用元组结构体语法 `struct Wrapper(Vec)` 创建了一个 `newtype` Wrapper,然后为它实现 `Display` 特征,最终实现了对 `Vec` 动态数组的格式化输出。 #### 更好的可读性及类型异化 -首先,更好的可读性不等于更少的代码(如果你学过Scala,相信会深有体会),其次下面的例子只是一个示例,未必能体现出更好的可读性: +首先,更好的可读性不等于更少的代码(如果你学过Scala,相信会深有体会),其次下面的例子只是一个示例,未必能体现出更好的可读性: ```rust use std::ops::Add; use std::fmt; @@ -57,10 +57,10 @@ impl Add for Meters { } fn main() { let d = calculate_distance(Meters(10), Meters(20)); - println!("{}",d); + println!("{}", d); } -fn calculate_distance(d1: Meters,d2: Meters) -> Meters { +fn calculate_distance(d1: Meters, d2: Meters) -> Meters { d1 + d2 } ``` @@ -79,11 +79,11 @@ struct Meters(u32); fn main() { let i: u32 = 2; - assert_eq!(i.pow(2),4); + assert_eq!(i.pow(2), 4); let n = Meters(i); // 下面的代码将报错,因为`Meters`类型上没有`pow`方法 - // assert_eq!(n.pow(2),4); + // assert_eq!(n.pow(2), 4); } ``` @@ -143,7 +143,7 @@ fn returns_long_type() -> Thunk { Bang!是不是?!立刻大幅简化了我们的使用。喝着奶茶、哼着歌、我写起代码撩起妹,何其快哉! -在标准库中,类型别名应用最广的就是简化 `Result` 枚举。 +在标准库中,类型别名应用最广的就是简化 `Result` 枚举。 例如在 `std::io` 库中,它定义了自己的 `Error` 类型:`std::io::Error`,那么如果要使用该 `Result` 就要用这样的语法:`std::result::Result;`,想象一下代码中充斥着这样的东东是一种什么感受?颤抖吧。。。 @@ -163,7 +163,7 @@ fn main() { let i = 2; let v = match i { 0..=3 => i, - _ => println!("不合规定的值:{}",i) + _ => println!("不合规定的值:{}", i) }; } ``` @@ -177,7 +177,7 @@ error[E0308]: `match` arms have incompatible types // match的分支类型不同 | _____________- 4 | | 0..3 => i, | | - this is found to be of type `{integer}` // 该分支返回整数类型 -5 | | _ => println!("不合规定的值:{}",i) +5 | | _ => println!("不合规定的值:{}", i) | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected integer, found `()` // 该分支返回()单元类型 6 | | }; | |_____- `match` arms have incompatible types @@ -191,7 +191,7 @@ fn main() { let i = 2; let v = match i { 0..=3 => i, - _ => panic!("不合规定的值:{}",i) + _ => panic!("不合规定的值:{}", i) }; } ``` @@ -238,7 +238,7 @@ fn generic(t: T) { } ``` -该函数很简单,就一个泛型参数T,那么如果保证 `T` 是固定大小的类型?仔细回想下,貌似在之前的课程章节中,我们也没有做过任何事情去做相关的限制,那 `T` 怎么就成了固定大小的类型了?奥秘在于编译器自动帮我们加上了 `Sized` 特征约束: +该函数很简单,就一个泛型参数T,那么如何保证 `T` 是固定大小的类型?仔细回想下,貌似在之前的课程章节中,我们也没有做过任何事情去做相关的限制,那 `T` 怎么就成了固定大小的类型了?奥秘在于编译器自动帮我们加上了 `Sized` 特征约束: ```rust fn generic(t: T) { // --snip-- diff --git a/contents/advance/smart-pointer/box.md b/contents/advance/smart-pointer/box.md index 090ca75b..1f863754 100644 --- a/contents/advance/smart-pointer/box.md +++ b/contents/advance/smart-pointer/box.md @@ -10,7 +10,7 @@ 与栈相反,堆上内存则是从低位地址向上增长,**堆内存通常只受物理内存限制**,而且通常是不连续的,因此从性能的角度看,栈往往比对堆更高。 -相比其它语言,Rust 堆上对象还有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制:当赋值时,发生的是所有权的转移(只需浅拷贝栈上的引用或智能指针即可),例如以下代码: +相比其它语言,Rust 堆上对象还有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制:当赋值时,发生的是所有权的转移(只需浅拷贝栈上的引用或智能指针即可),例如以下代码: ```rust fn main() { let b = foo("world"); @@ -50,7 +50,7 @@ fn foo(x: &str) -> String { fn main() { let a = Box::new(3); println!("a = {}", a); // a = 3 - + // 下面一行代码将报错 // let b = a + 1; // cannot add `{integer}` to `Box<{integer}>` } @@ -60,7 +60,7 @@ fn main() { - `println!` 可以正常打印出 `a` 的值,是因为它隐式地调用了 `Deref` 对智能指针 `a` 进行了解引用 - 最后一行代码 ` let b = a + 1` 报错,是因为在表达式中,我们无法自动隐式地执行 `Deref` 解引用操作,你需要使用 `*` 操作符 `let b = *a + 1`,来显式的进行解引用 -- `a` 持有的智能指针将在作用结束(`main` 函数结束)时,被释放掉,这是因为 `Box` 实现了 `Drop` 特征 +- `a` 持有的智能指针将在作用域结束(`main` 函数结束)时,被释放掉,这是因为 `Box` 实现了 `Drop` 特征 以上的例子在实际代码中其实很少会存在,因为将一个简单的值分配到堆上并没有太大的意义。将其分配在栈上,由于寄存器、CPU 缓存的原因,它的性能将更好,而且代码可读性也更好。 @@ -76,17 +76,17 @@ fn main() { let arr1 = arr; // arr 和 arr1 都拥有各自的栈上数组,因此不会报错 - println!("{:?}",arr.len()); - println!("{:?}",arr1.len()); + println!("{:?}", arr.len()); + println!("{:?}", arr1.len()); // 在堆上创建一个长度为1000的数组,然后使用一个智能指针指向它 let arr = Box::new([0;1000]); // 将堆上数组的所有权转移给 arr1,由于数据在堆上,因此仅仅拷贝了智能指针的结构体,底层数据并没有被拷贝 // 所有权顺利转移给 arr1,arr 不再拥有所有权 let arr1 = arr; - println!("{:?}",arr1.len()); + println!("{:?}", arr1.len()); // 由于 arr 不再拥有底层数组的所有权,因此下面代码将报错 - // println!("{:?}",arr.len()); + // println!("{:?}", arr.len()); } ``` @@ -205,7 +205,7 @@ fn main() { ```rust fn main() { let arr = vec![Box::new(1), Box::new(2)]; - let (first,second) = (&arr[0],&arr[1]); + let (first, second) = (&arr[0], &arr[1]); let sum = **first + **second; } ``` @@ -219,11 +219,11 @@ fn main() { ## Box::leak `Box` 中还提供了一个非常有用的关联函数:`Box::leak`,它可以消费掉 `Box` 并且强制目标值从内存中泄漏,读者可能会觉得,这有啥用啊? -其实还真有点用,例如,你可以把一个 `String` 类型,变成一个 `'static` 生命周期的 `&str` 类型: +其实还真有点用,例如,你可以把一个 `String` 类型,变成一个 `'static` 生命周期的 `&str` 类型: ```rust fn main() { let s = gen_static_str(); - println!("{}",s); + println!("{}", s); } fn gen_static_str() -> &'static str{ @@ -236,7 +236,7 @@ fn gen_static_str() -> &'static str{ 在之前的代码中,如果 `String` 创建于函数中,那么返回它的唯一方法就是转移所有权给调用者 `fn move_str() -> String`,而通过 `Box::leak` 我们不仅返回了一个 `&str` 字符串切片,它还是 `'static` 类型的! -要知道真正具有 `'static` 生命周期的往往都是编译期就创建的值,例如 `let v = "hello,world"`,这里 `v` 是直接打包到二进制可执行文件中的,因此该字符串具有 `'static` 生命周期,再比如 `const` 常量。 +要知道真正具有 `'static` 生命周期的往往都是编译期就创建的值,例如 `let v = "hello, world"`,这里 `v` 是直接打包到二进制可执行文件中的,因此该字符串具有 `'static` 生命周期,再比如 `const` 常量。 又有读者要问了,我还可以手动为变量标注 `'static` 啊。其实你标注的 `'static` 只是用来忽悠编译器的,但是超出作用域,一样被释放回收。而使用 `Box::leak` 就可以将一个运行期的值转为 `'static`。 @@ -246,7 +246,7 @@ fn gen_static_str() -> &'static str{ 那么我说一个简单的场景,**你需要一个在运行期初始化的值,但是可以全局有效,也就是和整个程序活得一样久**,那么久可以使用 `Box::leak`,例如有一个存储配置的结构体实例,它是在运行期动态插入内容,那么就可以将其转为全局有效,虽然 `Rc/Arc` 也可以实现此功能,但是 `Box::leak` 是性能最高的。 ## 总结 -`Box` 背后是调用 `jemalloc` 来做内存管理,所以堆上的空间无需我们的手动管理。与此类似,带 GC 的语言中的对象也是借助于 `Box` 概念来实现的,一切皆对象 = 一切皆 Box, 只不过我们无需自己去 `Box` 罢了。 +`Box` 背后是调用 `jemalloc` 来做内存管理,所以堆上的空间无需我们的手动管理。与此类似,带 GC 的语言中的对象也是借助于 `Box` 概念来实现的,**一切皆对象 = 一切皆 Box**, 只不过我们无需自己去 `Box` 罢了。 其实很多时候,编译器的鞭笞可以助我们更快的成长,例如所有权规则里的借用、move、生命周期就是编译器在教我们做人,哦不是,是教我们深刻理解堆栈、内存布局、作用域等等你在其它 GC 语言无需去关注的东西。刚开始是很痛苦,但是一旦熟悉了这套规则,写代码的效率和代码本身的质量将飞速上升,直到你可以用 Java 开发的效率写出 Java 代码不可企及的性能和安全性,最终 Rust 语言所谓的开发效率低、心智负担高,对你来说终究不是个事。 diff --git a/contents/basic/formatted-output.md b/contents/basic/formatted-output.md index 734fe140..c36fa0c1 100644 --- a/contents/basic/formatted-output.md +++ b/contents/basic/formatted-output.md @@ -26,10 +26,10 @@ println!("{:04}", 42); // => "0042" with leading zeros ```rust fn main() { let s = "hello"; - println!("{}, world",s); + println!("{}, world", s); let s1 = format!("{}, world", s); - print!("{}",s1); - print!("{}\n","!"); + print!("{}", s1); + print!("{}\n", "!"); } ``` @@ -48,7 +48,7 @@ eprintln!("Error: Could not complete task") 它们仅应该被用于输出错误信息和进度信息,其它场景都应该使用 `print!` 系列。 ## {} 与 {:?} -与其它语言常用的 `%d`,`%s` 不同,Rust 特立独行地选择了 `{}` 作为格式化占位符(说到这个,有点想吐槽下,Rust 中自创的概念其实还挺多的,真不知道该夸奖还是该吐槽-,-),事实证明,这种选择非常正确,它帮助用户减少了很多使用成本,你无需再为特定的类型选择特定的占位符,统一用 `{}` 来替代即可,剩下的类型推导等细节只要交给 Rust 去做。 +与其它语言常用的 `%d`,`%s` 不同,Rust 特立独行地选择了 `{}` 作为格式化占位符(说到这个,有点想吐槽下,Rust 中自创的概念其实还挺多的,真不知道该夸奖还是该吐槽-,-),事实证明,这种选择非常正确,它帮助用户减少了很多使用成本,你无需再为特定的类型选择特定的占位符,统一用 `{}` 来替代即可,剩下的类型推导等细节只要交给 Rust 去做。 与 `{}` 类似,`{:?}` 也是占位符: @@ -69,9 +69,9 @@ struct Person { 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); + let v = vec![1, 2, 3]; + let p = Person{name: "sunface".to_string(), age: 18}; + println!("{:?}, {:?}, {:?}, {:?}", i, s, v, p); } ``` @@ -88,7 +88,7 @@ let p = Person { name: "sunface".to_string(), age: 18, }; -println!("{}, {}, {},{}", i, s, v, p); +println!("{}, {}, {}, {}", i, s, v, p); ``` 运行后可以看到 `v` 和 `p` 都无法通过编译,因为没有实现 `Display` 特征,但是你又不能像派生 `Debug` 一般派生 `Display`,只能另寻他法: @@ -103,14 +103,14 @@ println!("{}, {}, {},{}", i, s, v, p); `{:#?}` 与 `{:?}` 几乎一样,唯一的区别在于它能更优美地输出内容: ```console // {:?} -[1, 2, 3],Person { name: "sunface", age: 18 } +[1, 2, 3], Person { name: "sunface", age: 18 } // {:#?} [ 1, 2, 3, -],Person { +], Person { name: "sunface", } ``` @@ -177,11 +177,11 @@ fn main() { 除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如 `{1}`,表示用第二个参数替换该占位符(索引从0开始): ```rust fn main() { - println!("{}{}",1,2); // =>"12" - println!("{1}{0}",1,2); // =>"21" + 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 + println!("{1}{}{0}{}", 1, 2); // => 2112 } ``` @@ -200,15 +200,25 @@ fn main() { 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 fn main() { let v = 3.1415926; // Display => 3.14 - println!("{:.2}",v); + println!("{:.2}", v); // Debug => 3.14 - println!("{:.2?}",v); + println!("{:.2?}", v); } ``` @@ -260,11 +270,11 @@ fn main() { fn main() { // 以下全部都会补齐5个字符的长度 // 左对齐 => Hello x ! - println!("Hello {:<5}!","x"); + println!("Hello {:<5}!", "x"); // 右对齐 => Hello x - println!("Hello {:>5}!","x"); + println!("Hello {:>5}!", "x"); // 居中对齐 => Hello x ! - println!("Hello {:^5}!","x"); + println!("Hello {:^5}!", "x"); // 对齐并使用指定符号填充 => Hello x&&&&! // 指定符号填充的前提条件是必须有对齐字符 @@ -278,12 +288,12 @@ fn main() { fn main() { let v = 3.1415926; // 保留小数点后两位 => 3.14 - println!("{:.2}",v); + println!("{:.2}", v); // 带符号保留小数点后两位 => +3.14 - println!("{:+.2}",v); + println!("{:+.2}", v); // 不带小数 => 3 - println!("{:.0}",v); - // 通过参数来设定精度 => 3.1416,相当于{:.4} + println!("{:.0}", v); + // 通过参数来设定精度 => 3.1416,相当于{:.4} println!("{:.1$}", v, 4); let s = "hi我是Sunface孙飞"; @@ -334,8 +344,8 @@ fn main() { #### 指针地址 ```rust -let v= vec![1,2,3]; -println!("{:p}",v.as_ptr()) // => 0x600002324050 +let v= vec![1, 2, 3]; +println!("{:p}", v.as_ptr()) // => 0x600002324050 ``` #### 转义 @@ -350,7 +360,7 @@ fn main() { } ``` -## 在格式化字符串时捕获环境中的值(Rust1.58新增) +## 在格式化字符串时捕获环境中的值(Rust 1.58 新增) 在以前,想要输出一个函数的返回值,你需要这么做: ```rust @@ -364,7 +374,7 @@ fn main() { println!("Hello, {person}!", person = p); } ``` -问题倒也不大,但是一旦格式化字符串长了后,就会非常冗余,而在1.58后,我们可以这么写: +问题倒也不大,但是一旦格式化字符串长了后,就会非常冗余,而在 1.58 后,我们可以这么写: ```rust fn get_person() -> String { String::from("sunface") @@ -381,8 +391,8 @@ for (name, score) in get_scores() { println!("{name}: {score:width$.precision$}"); } ``` -但也有局限,它只能捕获普通的变量,对于更复杂的类型(例如表达式),可以先将它赋值给一个变量或使用以前的`name = expression`形式的格式化参数。 -目前除了`panic!`外,其它接收格式化参数的宏,都可以使用新的特性。对于`panic!` 而言,如果还在使用`Rust2015`或`2018`大版本 ,那`panic!("{ident}")`依然会被当成 正常的字符串来处理,同时编译器会给予`warn`提示。而对于`2021版本`,则可以正常使用: +但也有局限,它只能捕获普通的变量,对于更复杂的类型(例如表达式),可以先将它赋值给一个变量或使用以前的 `name = expression` 形式的格式化参数。 +目前除了 `panic!` 外,其它接收格式化参数的宏,都可以使用新的特性。对于 `panic!` 而言,如果还在使用 `2015版本` 或 `2018版本`,那 `panic!("{ident}")` 依然会被当成 正常的字符串来处理,同时编译器会给予 `warn` 提示。而对于 `2021版本` ,则可以正常使用: ```rust fn get_person() -> String { String::from("sunface")