From 7f827e9fabc768dc23878b13c677d561f9c7364e Mon Sep 17 00:00:00 2001 From: KaiserY Date: Mon, 7 Feb 2022 11:37:16 +0800 Subject: [PATCH] update to ch05-03 --- .../listing-04-08/src/main.rs | 8 +- .../listing-04-09/src/main.rs | 12 +- .../no-listing-19-slice-error/src/main.rs | 2 +- src/ch04-03-slices.md | 139 +++------------- src/ch05-00-structs.md | 4 +- src/ch05-01-defining-structs.md | 153 +++--------------- src/ch05-02-example-structs.md | 136 +++------------- src/ch05-03-method-syntax.md | 96 +---------- 8 files changed, 86 insertions(+), 464 deletions(-) diff --git a/listings/ch04-understanding-ownership/listing-04-08/src/main.rs b/listings/ch04-understanding-ownership/listing-04-08/src/main.rs index b6182fe..e8f8f4c 100755 --- a/listings/ch04-understanding-ownership/listing-04-08/src/main.rs +++ b/listings/ch04-understanding-ownership/listing-04-08/src/main.rs @@ -14,11 +14,11 @@ fn first_word(s: &String) -> usize { fn main() { let mut s = String::from("hello world"); - let word = first_word(&s); // word will get the value 5 + let word = first_word(&s); // word 的值为 5 - s.clear(); // this empties the String, making it equal to "" + s.clear(); // 这清空了字符串,使其等于 "" - // word still has the value 5 here, but there's no more string that - // we could meaningfully use the value 5 with. word is now totally invalid! + // word 在此处的值仍然是 5, + // 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效! } // ANCHOR_END: here diff --git a/listings/ch04-understanding-ownership/listing-04-09/src/main.rs b/listings/ch04-understanding-ownership/listing-04-09/src/main.rs index 5a6ceaa..2b71461 100755 --- a/listings/ch04-understanding-ownership/listing-04-09/src/main.rs +++ b/listings/ch04-understanding-ownership/listing-04-09/src/main.rs @@ -16,21 +16,21 @@ fn first_word(s: &str) -> &str { fn main() { let my_string = String::from("hello world"); - // `first_word` works on slices of `String`s, whether partial or whole + // `first_word` 适用于 `String`(的 slice),整体或全部 let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); - // `first_word` also works on references to `String`s, which are equivalent - // to whole slices of `String`s + // `first_word` 也适用于 `String` 的引用, + // 这等价于真个 `String` 的 slice let word = first_word(&my_string); let my_string_literal = "hello world"; - // `first_word` works on slices of string literals, whether partial or whole + // `first_word` 适用于字符串字面值,整体或全部 let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]); - // Because string literals *are* string slices already, - // this works too, without the slice syntax! + // 因为字符串字面值已经 **是** 字符串 slice 了, + // 这也是适用的,无需 slice 语法! let word = first_word(my_string_literal); } // ANCHOR_END: usage diff --git a/listings/ch04-understanding-ownership/no-listing-19-slice-error/src/main.rs b/listings/ch04-understanding-ownership/no-listing-19-slice-error/src/main.rs index 99e0401..dca7f63 100755 --- a/listings/ch04-understanding-ownership/no-listing-19-slice-error/src/main.rs +++ b/listings/ch04-understanding-ownership/no-listing-19-slice-error/src/main.rs @@ -16,7 +16,7 @@ fn main() { let word = first_word(&s); - s.clear(); // error! + s.clear(); // 错误! println!("the first word is: {}", word); } diff --git a/src/ch04-03-slices.md b/src/ch04-03-slices.md index a2ffe95..7f97780 100644 --- a/src/ch04-03-slices.md +++ b/src/ch04-03-slices.md @@ -2,34 +2,24 @@ > [ch04-03-slices.md](https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md) >
-> commit d377d2effa9ae9173026e35a7e464b8f5b82409a +> commit a5e0c5b2c5f9054be3b961aea2c7edfeea591de8 -另一个没有所有权的数据类型是 *slice*。slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。 +*slice* 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它没有所有权。 这里有一个编程小习题:编写一个函数,该函数接收一个字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。 -让我们考虑一下这个函数的签名: +让我们推敲下如何不用 slice 编写这个函数的签名,来理解 slice 能解决的问题: ```rust,ignore fn first_word(s: &String) -> ? ``` -`first_word` 函数有一个参数 `&String`。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取 **部分** 字符串的办法。不过,我们可以返回单词结尾的索引。试试如示例 4-7 中的代码。 +`first_word` 函数有一个参数 `&String`。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取 **部分** 字符串的办法。不过,我们可以返回单词结尾的索引,结尾由一个空格表示。试试如示例 4-7 中的代码。 文件名: src/main.rs ```rust -fn first_word(s: &String) -> usize { - let bytes = s.as_bytes(); - - for (i, &item) in bytes.iter().enumerate() { - if item == b' ' { - return i; - } - } - - s.len() -} +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:here}} ``` 示例 4-7:`first_word` 函数返回 `String` 参数的一个字节索引值 @@ -37,28 +27,23 @@ fn first_word(s: &String) -> usize { 因为需要逐个元素的检查 `String` 中的值是否为空格,需要用 `as_bytes` 方法将 `String` 转化为字节数组: ```rust,ignore -let bytes = s.as_bytes(); +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:as_bytes}} ``` 接下来,使用 `iter` 方法在字节数组上创建一个迭代器: ```rust,ignore -for (i, &item) in bytes.iter().enumerate() { +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:iter}} ``` -我们将在第十三章详细讨论迭代器。现在,只需知道 `iter` 方法返回集合中的每一个元素,而 `enumerate` 包装了 `iter` 的结果,将这些元素作为元组的一部分来返回。`enumerate` 返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。这比我们自己计算索引要方便一些。 +我们将在[第十三章][ch13]详细讨论迭代器。现在,只需知道 `iter` 方法返回集合中的每一个元素,而 `enumerate` 包装了 `iter` 的结果,将这些元素作为元组的一部分来返回。`enumerate` 返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。这比我们自己计算索引要方便一些。 -因为 `enumerate` 方法返回一个元组,我们可以使用模式来解构,我们将在第 6 章中进一步讨论有关模式的问题。所以在 `for` 循环中,我们指定了一个模式,其中元组中的 `i` 是索引而元组中的 `&item` 是单个字节。因为我们从 `.iter().enumerate()` 中获取了集合元素的引用,所以模式中使用了 `&`。 +因为 `enumerate` 方法返回一个元组,我们可以使用模式来解构,我们将在[第六章][ch6]中进一步讨论有关模式的问题。所以在 `for` 循环中,我们指定了一个模式,其中元组中的 `i` 是索引而元组中的 `&item` 是单个字节。因为我们从 `.iter().enumerate()` 中获取了集合元素的引用,所以模式中使用了 `&`。 在 `for` 循环中,我们通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 `s.len()` 返回字符串的长度: ```rust,ignore - if item == b' ' { - return i; - } -} - -s.len() +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:inside_for}} ``` 现在有了一个找到字符串中第一个单词结尾索引的方法,不过这有一个问题。我们返回了一个独立的 `usize`,不过它只在 `&String` 的上下文中才是一个有意义的数字。换句话说,因为它是一个与 `String` 相分离的值,无法保证将来它仍然有效。考虑一下示例 4-8 中使用了示例 4-7 中 `first_word` 函数的程序。 @@ -66,28 +51,7 @@ s.len() 文件名: src/main.rs ```rust -# fn first_word(s: &String) -> usize { -# let bytes = s.as_bytes(); -# -# for (i, &item) in bytes.iter().enumerate() { -# if item == b' ' { -# return i; -# } -# } -# -# s.len() -# } -# -fn main() { - let mut s = String::from("hello world"); - - let word = first_word(&s); // word 的值为 5 - - s.clear(); // 这清空了字符串,使其等于 "" - - // word 在此处的值仍然是 5, - // 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效! -} +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-08/src/main.rs:here}} ``` 示例 4-8:存储 `first_word` 函数调用的返回值并接着改变 `String` 的内容 @@ -109,15 +73,10 @@ fn second_word(s: &String) -> (usize, usize) { **字符串 slice**(*string slice*)是 `String` 中一部分值的引用,它看起来像这样: ```rust -let s = String::from("hello world"); - -let hello = &s[0..5]; -let world = &s[6..11]; +{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-17-slice/src/main.rs:here}} ``` -这类似于引用整个 `String` 不过带有额外的 `[0..5]` 部分。它不是对整个 `String` 的引用,而是对部分 `String` 的引用。 - -可以使用一个由中括号中的 `[starting_index..ending_index]` 指定的 range 创建一个 slice,其中 `starting_index` 是 slice 的第一个位置,`ending_index` 则是 slice 最后一个位置的后一个值。在其内部,slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 `ending_index` 减去 `starting_index` 的值。所以对于 `let world = &s[6..11];` 的情况,`world` 将是一个包含指向 `s` 索引 6 的指针和长度值 5 的 slice。 +不同于整个 `String` 的引用,`hello` 是一个部分 `String` 的引用,由一个额外的 `[0..5]` 部分指定。可以使用一个由中括号中的 `[starting_index..ending_index]` 指定的 range 创建一个 slice,其中 `starting_index` 是 slice 的第一个位置,`ending_index` 则是 slice 最后一个位置的后一个值。在其内部,slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 `ending_index` 减去 `starting_index` 的值。所以对于 `let world = &s[6..11];` 的情况,`world` 将是一个包含指向 `s` 索引 6 的指针和长度值 5 的 slice。 图 4-6 展示了一个图例。 @@ -163,17 +122,7 @@ let slice = &s[..]; 文件名: src/main.rs ```rust -fn first_word(s: &String) -> &str { - let bytes = s.as_bytes(); - - for (i, &item) in bytes.iter().enumerate() { - if item == b' ' { - return &s[0..i]; - } - } - - &s[..] -} +{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-18-first-word-slice/src/main.rs:here}} ``` 我们使用跟示例 4-7 相同的方式获取单词结尾的索引,通过寻找第一个出现的空格。当找到一个空格,我们返回一个字符串 slice,它使用字符串的开始和空格的索引作为开始和结束的索引。 @@ -191,36 +140,13 @@ fn second_word(s: &String) -> &str { 文件名: src/main.rs ```rust,ignore,does_not_compile -fn main() { - let mut s = String::from("hello world"); - - let word = first_word(&s); - - s.clear(); // 错误! - - println!("the first word is: {}", word); -} +{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-19-slice-error/src/main.rs:here}} ``` 这里是编译错误: ```console -$ cargo run - Compiling ownership v0.1.0 (file:///projects/ownership) -error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable - --> src/main.rs:18:5 - | -16 | let word = first_word(&s); - | -- immutable borrow occurs here -17 | -18 | s.clear(); // error! - | ^^^^^^^^^ mutable borrow occurs here -19 | -20 | println!("the first word is: {}", word); - | ---- immutable borrow later used here - -For more information about this error, try `rustc --explain E0502`. -error: could not compile `ownership` due to previous error +{{#include ../listings/ch04-understanding-ownership/no-listing-19-slice-error/output.txt}} ``` 回忆一下借用规则,当拥有某值的不可变引用时,就不能再获取一个可变引用。因为 `clear` 需要清空 `String`,它尝试获取一个可变引用。在调用 `clear` 之后的 `println!` 使用了 `word` 中的引用,所以这个不可变的引用在此时必须仍然有效。Rust 不允许 `clear` 中的可变引用和 `word` 中的不可变引用同时存在,因此编译失败。Rust 不仅使得我们的 API 简单易用,也在编译时就消除了一整类的错误! @@ -246,7 +172,7 @@ fn first_word(s: &String) -> &str { 而更有经验的 Rustacean 会编写出示例 4-9 中的签名,因为它使得可以对 `String` 值和 `&str` 值使用相同的函数: ```rust,ignore -fn first_word(s: &str) -> &str { +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-09/src/main.rs:here}} ``` 示例 4-9: 通过将 `s` 参数的类型改为字符串 slice 来改进 `first_word` 函数 @@ -256,32 +182,7 @@ fn first_word(s: &str) -> &str { 文件名: src/main.rs ```rust -# fn first_word(s: &str) -> &str { -# let bytes = s.as_bytes(); -# -# for (i, &item) in bytes.iter().enumerate() { -# if item == b' ' { -# return &s[0..i]; -# } -# } -# -# &s[..] -# } -fn main() { - let my_string = String::from("hello world"); - - // first_word 中传入 `String` 的 slice - let word = first_word(&my_string[..]); - - let my_string_literal = "hello world"; - - // first_word 中传入字符串字面值的 slice - let word = first_word(&my_string_literal[..]); - - // 因为字符串字面值 **就是** 字符串 slice, - // 这样写也可以,即不使用 slice 语法! - let word = first_word(my_string_literal); -} +{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-09/src/main.rs:usage}} ``` ### 其他类型的 slice @@ -310,5 +211,7 @@ assert_eq!(slice, &[2, 3]); 所有权系统影响了 Rust 中很多其他部分的工作方式,所以我们还会继续讲到这些概念,这将贯穿本书的余下内容。让我们开始第五章,来看看如何将多份数据组合进一个 `struct` 中。 +[ch13]: ch13-02-iterators.html +[ch6]: ch06-02-match.html#绑定值的模式 [strings]: ch08-02-strings.html#使用字符串存储-utf-8-编码的文本 -[deref-coercions]: ch15-02-deref.html#函数和方法的隐式-deref-强制转换 \ No newline at end of file +[deref-coercions]: ch15-02-deref.html#函数和方法的隐式-deref-强制转换 diff --git a/src/ch05-00-structs.md b/src/ch05-00-structs.md index 777a8a1..6e06488 100644 --- a/src/ch05-00-structs.md +++ b/src/ch05-00-structs.md @@ -2,6 +2,6 @@ > [ch05-00-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-00-structs.md) >
-> commit dacde2200cc047b956b3c7365c8c6253de4425c0 +> commit 8a0bb3c96e71927b80fa2286d7a5a5f2547c6aa4 -*struct*,或者 *structure*,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,*struct* 就像对象中的数据属性。在本章中,我们会对元组和结构体进行比较和对比。还将演示如何定义和实例化结构体,并讨论如何定义关联函数,特别是被称为 *方法* 的那种关联函数,以指定与结构体类型相关的行为。你可以在程序中基于结构体和枚举(*enum*)(在第六章介绍)创建新类型,以充分利用 Rust 的编译时类型检查。 +*struct*,或者 *structure*,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,*struct* 就像对象中的数据属性。在本章中,我们会对元组和结构体进行比较和对比。还将演示如何定义和实例化结构体,并讨论如何定义关联函数,特别是被称为 *方法* 的那种关联函数,以指定与结构体类型相关的行为。你可以在程序中基于结构体和枚举(*enum*)(在第六章介绍)创建新类型,以充分利用 Rust 的编译时类型检查。 diff --git a/src/ch05-01-defining-structs.md b/src/ch05-01-defining-structs.md index 1013ae3..83a6801 100644 --- a/src/ch05-01-defining-structs.md +++ b/src/ch05-01-defining-structs.md @@ -2,19 +2,14 @@ > [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-01-defining-structs.md) >
-> commit 794d33a8716fe814d93137fa7112747c2b76e723 +> commit 4f7799e3b3f0510440050f74af3dd75545c2dd23 -结构体和我们在[“元组类型”][tuples]部分论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。 +结构体和我们在[“元组类型”][tuples]部分论过的元组类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。 定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 **字段**(*field*)。例如,示例 5-1 展示了一个存储用户账号信息的结构体: ```rust -struct User { - username: String, - email: String, - sign_in_count: u64, - active: bool, -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-01/src/main.rs:here}} ``` 示例 5-1:`User` 结构体定义 @@ -22,19 +17,7 @@ struct User { 一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字,value 是需要存储在字段中的数据值。实例中字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户: ```rust -# struct User { -# username: String, -# email: String, -# sign_in_count: u64, -# active: bool, -# } -# -let user1 = User { - email: String::from("someone@example.com"), - username: String::from("someusername123"), - active: true, - sign_in_count: 1, -}; +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-02/src/main.rs:here}} ``` 示例 5-2:创建 `User` 结构体的实例 @@ -42,21 +25,7 @@ let user1 = User { 为了从结构体中获取某个特定的值,可以使用点号。如果我们只想要用户的邮箱地址,可以用 `user1.email`。要更改结构体中的值,如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。示例 5-3 展示了如何改变一个可变的 `User` 实例 `email` 字段的值: ```rust -# struct User { -# username: String, -# email: String, -# sign_in_count: u64, -# active: bool, -# } -# -let mut user1 = User { - email: String::from("someone@example.com"), - username: String::from("someusername123"), - active: true, - sign_in_count: 1, -}; - -user1.email = String::from("anotheremail@example.com"); +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-03/src/main.rs:here}} ``` 示例 5-3:改变 `User` 实例 `email` 字段的值 @@ -66,47 +35,19 @@ user1.email = String::from("anotheremail@example.com"); 示例 5-4 显示了一个 `build_user` 函数,它返回一个带有给定的 email 和用户名的 `User` 结构体实例。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`。 ```rust -# struct User { -# username: String, -# email: String, -# sign_in_count: u64, -# active: bool, -# } -# -fn build_user(email: String, username: String) -> User { - User { - email: email, - username: username, - active: true, - sign_in_count: 1, - } -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-04/src/main.rs:here}} ``` 示例 5-4:`build_user` 函数获取 email 和用户名并返回 `User` 实例 为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复 `email` 和 `username` 字段名称与变量有些啰嗦。如果结构体有更多字段,重复每个名称就更加烦人了。幸运的是,有一个方便的简写语法! -### 变量与字段同名时的字段初始化简写语法 +### 使用字段初始化简写语法 因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法**(*field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email` 和 `username` 了,如示例 5-5 所示。 ```rust -# struct User { -# username: String, -# email: String, -# sign_in_count: u64, -# active: bool, -# } -# -fn build_user(email: String, username: String) -> User { - User { - email, - username, - active: true, - sign_in_count: 1, - } -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-05/src/main.rs:here}} ``` 示例 5-5:`build_user` 函数使用了字段初始化简写语法,因为 `email` 和 `username` 参数与结构体字段同名 @@ -120,26 +61,7 @@ fn build_user(email: String, username: String) -> User { 首先,示例 5-6 展示了不使用更新语法时,如何在 `user2` 中创建一个新 `User` 实例。我们为 `email` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值: ```rust -# struct User { -# username: String, -# email: String, -# sign_in_count: u64, -# active: bool, -# } -# -# let user1 = User { -# email: String::from("someone@example.com"), -# username: String::from("someusername123"), -# active: true, -# sign_in_count: 1, -# }; -# -let user2 = User { - active: user1.active, - username: user1.username, - email: String::from("another@example.com"), - sign_in_count: user1.sign_in_count, -}; +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-06/src/main.rs:here}} ``` 示例 5-6:使用 `user1` 中的一个值创建一个新的 `User` 实例 @@ -147,24 +69,7 @@ let user2 = User { 使用结构体更新语法,我们可以通过更少的代码来达到相同的效果,如示例 5-7 所示。`..` 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。 ```rust -# struct User { -# username: String, -# email: String, -# sign_in_count: u64, -# active: bool, -# } -# -# let user1 = User { -# email: String::from("someone@example.com"), -# username: String::from("someusername123"), -# active: true, -# sign_in_count: 1, -# }; -# -let user2 = User { - email: String::from("another@example.com"), - ..user1 -}; +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-07/src/main.rs:here}} ``` 示例 5-7:使用结构体更新语法为一个 `User` 实例设置一个新的 `email` 值,不过其余值来自 `user1` 变量中实例的字段 @@ -180,11 +85,7 @@ let user2 = User { 要定义元组结构体,以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 `Color` 和 `Point` 元组结构体的定义和用法: ```rust -struct Color(i32, i32, i32); -struct Point(i32, i32, i32); - -let black = Color(0, 0, 0); -let origin = Point(0, 0, 0); +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-01-tuple-structs/src/main.rs}} ``` 注意 `black` 和 `origin` 值的类型不同,因为它们是不同的元组结构体的实例。你定义的每一个结构体有其自己的类型,即使结构体中的字段有着相同的类型。例如,一个获取 `Color` 类型参数的函数不能接受 `Point` 作为参数,即便这两个类型都由三个 `i32` 值组成。在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用 `.` 后跟索引来访问单独的值,等等。 @@ -194,9 +95,7 @@ let origin = Point(0, 0, 0); 我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体**(*unit-like structs*)因为它们类似于 `()`,即[“元组类型”][tuples]一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。下面是一个声明和实例化一个名为 `AlwaysEqual` 的 unit 结构的例子。 ```rust -struct AlwaysEqual; - -let subject = AlwaysEqual; +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-04-unit-like-structs/src/main.rs}} ``` 要定义 `AlwaysEqual`,我们使用 `struct` 关键字,我们想要的名称,然后是一个分号。不需要花括号或圆括号!然后,我们可以以类似的方式在 `subject` 变量中获得 `AlwaysEqual` 的实例:使用我们定义的名称,不需要任何花括号或圆括号。想象一下,我们将实现这个类型的行为,即每个实例始终等于每一个其他类型的实例,也许是为了获得一个已知的结果以便进行测试。我们不需要任何数据来实现这种行为,你将在第十章中,看到如何定义特性并在任何类型上实现它们,包括类单元结构体。 @@ -211,10 +110,10 @@ let subject = AlwaysEqual; > > ```rust,ignore,does_not_compile > struct User { +> active: bool, > username: &str, > email: &str, > sign_in_count: u64, -> active: bool, > } > > fn main() { @@ -233,36 +132,34 @@ let subject = AlwaysEqual; > $ cargo run > Compiling structs v0.1.0 (file:///projects/structs) > error[E0106]: missing lifetime specifier -> --> src/main.rs:2:15 +> --> src/main.rs:3:15 > | -> 2 | username: &str, +> 3 | username: &str, > | ^ expected named lifetime parameter > | > help: consider introducing a named lifetime parameter > | -> 1 | struct User<'a> { -> 2 | username: &'a str, +> 1 ~ struct User<'a> { +> 2 | active: bool, +> 3 ~ username: &'a str, > | > > error[E0106]: missing lifetime specifier -> --> src/main.rs:3:12 +> --> src/main.rs:4:12 > | -> 3 | email: &str, +> 4 | email: &str, > | ^ expected named lifetime parameter > | > help: consider introducing a named lifetime parameter > | -> 1 | struct User<'a> { -> 2 | username: &str, -> 3 | email: &'a str, +> 1 ~ struct User<'a> { +> 2 | active: bool, +> 3 | username: &str, +> 4 ~ email: &'a str, > | > -> error: aborting due to 2 previous errors -> > For more information about this error, try `rustc --explain E0106`. -> error: could not compile `structs` -> -> To learn more, run the command again with --verbose. +> error: could not compile `structs` due to 2 previous errors > ``` > > 第十章会讲到如何修复这个问题以便在结构体中存储引用,不过现在,我们会使用像 `String` 这类拥有所有权的类型来替代 `&str` 这样的引用以修正这个错误。 diff --git a/src/ch05-02-example-structs.md b/src/ch05-02-example-structs.md index 51390fa..babf37b 100644 --- a/src/ch05-02-example-structs.md +++ b/src/ch05-02-example-structs.md @@ -11,19 +11,7 @@ 文件名: src/main.rs ```rust -fn main() { - let width1 = 30; - let height1 = 50; - - println!( - "The area of the rectangle is {} square pixels.", - area(width1, height1) - ); -} - -fn area(width: u32, height: u32) -> u32 { - width * height -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:all}} ``` 示例 5-8:通过分别指定长方形的宽和高的变量来计算长方形面积 @@ -31,11 +19,7 @@ fn area(width: u32, height: u32) -> u32 { 现在使用 `cargo run` 运行程序: ```console -$ cargo run - Compiling rectangles v0.1.0 (file:///projects/rectangles) - Finished dev [unoptimized + debuginfo] target(s) in 0.42s - Running `target/debug/rectangles` -The area of the rectangle is 1500 square pixels. +{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/output.txt}} ``` 这个示例代码在调用 `area` 函数时传入每个维度,虽然可以正确计算出长方形的面积,但我们仍然可以修改这段代码来使它的意义更加明确,并且增加可读性。 @@ -43,7 +27,7 @@ The area of the rectangle is 1500 square pixels. 这些代码的问题突显在 `area` 的签名上: ```rust,ignore -fn area(width: u32, height: u32) -> u32 { +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:here}} ``` 函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 [“元组类型”][the-tuple-type] 部分已经讨论过了一种可行的方法:元组。 @@ -55,18 +39,7 @@ fn area(width: u32, height: u32) -> u32 { 文件名: src/main.rs ```rust -fn main() { - let rect1 = (30, 50); - - println!( - "The area of the rectangle is {} square pixels.", - area(rect1) - ); -} - -fn area(dimensions: (u32, u32)) -> u32 { - dimensions.0 * dimensions.1 -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-09/src/main.rs}} ``` 示例 5-9:使用元组来指定长方形的宽高 @@ -82,23 +55,7 @@ fn area(dimensions: (u32, u32)) -> u32 { 文件名: src/main.rs ```rust -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - - println!( - "The area of the rectangle is {} square pixels.", - area(&rect1) - ); -} - -fn area(rectangle: &Rectangle) -> u32 { - rectangle.width * rectangle.height -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-10/src/main.rs}} ``` 示例 5-10:定义 `Rectangle` 结构体 @@ -116,48 +73,37 @@ fn area(rectangle: &Rectangle) -> u32 { 文件名: src/main.rs ```rust,ignore,does_not_compile -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - - println!("rect1 is {}", rect1); -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/src/main.rs}} ``` 示例 5-11:尝试打印出 `Rectangle` 实例 当我们运行这个代码时,会出现带有如下核心信息的错误: -```console -error[E0277]: `Rectangle` doesn't implement `std::fmt::Display` +```text +{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:3}} ``` `println!` 宏能处理很多类型的格式,不过,`{}` 默认告诉 `println!` 使用被称为 `Display` 的格式:意在提供给直接终端用户查看的输出。目前为止见过的基本类型都默认实现了 `Display`,因为它就是向用户展示 `1` 或其他任何基本类型的唯一方式。不过对于结构体,`println!` 应该用来输出的格式是不明确的,因为这有更多显示的可能性:是否需要逗号?需要打印出大括号吗?所有字段都应该显示吗?由于这种不确定性,Rust 不会尝试猜测我们的意图,所以结构体并没有提供一个 `Display` 实现来使用 `println!` 与 `{}` 占位符。 但是如果我们继续阅读错误,将会发现这个有帮助的信息: -```console -= help: the trait `std::fmt::Display` is not implemented for `Rectangle` -= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead +```text +{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:9:10}} ``` 让我们来试试!现在 `println!` 宏调用看起来像 `println!("rect1 is {:?}", rect1);` 这样。在 `{}` 中加入 `:?` 指示符告诉 `println!` 我们想要使用叫做 `Debug` 的输出格式。`Debug` 是一个 trait,它允许我们以一种对开发者有帮助的方式打印结构体,以便当我们调试代码时能看到它的值。 这样调整后再次运行程序。见鬼了!仍然能看到一个错误: -```console -error[E0277]: `Rectangle` doesn't implement `Debug` +```text +{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:3}} ``` 不过编译器又一次给出了一个有帮助的信息: -```console -= help: the trait `Debug` is not implemented for `Rectangle` -= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle` +```text +{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:9:10}} ``` Rust **确实** 包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,在结构体定义之前加上外部属性 `#[derive(Debug)]`,如示例 5-12 所示: @@ -165,17 +111,7 @@ Rust **确实** 包含了打印出调试信息的功能,不过我们必须为 文件名: src/main.rs ```rust -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - - println!("rect1 is {:?}", rect1); -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/src/main.rs}} ``` 示例 5-12:增加属性来派生 `Debug` trait,并使用调试格式打印 `Rectangle` 实例 @@ -183,24 +119,13 @@ fn main() { 现在我们再运行这个程序时,就不会有任何错误,并会出现如下输出: ```console -$ cargo run - Compiling rectangles v0.1.0 (file:///projects/rectangles) - Finished dev [unoptimized + debuginfo] target(s) in 0.48s - Running `target/debug/rectangles` -rect1 is Rectangle { width: 30, height: 50 } +{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/output.txt}} ``` 好极了!这并不是最漂亮的输出,不过它显示这个实例的所有字段,毫无疑问这对调试有帮助。当我们有一个更大的结构体时,能有更易读一点的输出就好了,为此可以使用 `{:#?}` 替换 `println!` 字符串中的 `{:?}`。在这个例子中使用 `{:#?}` 风格将会输出: ```console -$ cargo run - Compiling rectangles v0.1.0 (file:///projects/rectangles) - Finished dev [unoptimized + debuginfo] target(s) in 0.48s - Running `target/debug/rectangles` -rect1 is Rectangle { - width: 30, - height: 50, -} +{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-02-pretty-debug/output.txt}} ``` 另一种使用 `Debug` 格式打印数值的方法是使用 [`dbg!` 宏][dbg]。`dbg!` 宏接收一个表达式的所有权,打印出代码中调用 dbg! 宏时所在的文件和行号,以及该表达式的结果值,并返回该值的所有权。 @@ -210,34 +135,13 @@ rect1 is Rectangle { 下面是一个例子,我们对分配给 `width` 字段的值以及 `rect1` 中整个结构的值感兴趣。 ```rust -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let scale = 2; - let rect1 = Rectangle { - width: dbg!(30 * scale), - height: 50, - }; - - dbg!(&rect1); -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/src/main.rs}} ``` + 我们可以把 `dbg!` 放在表达式 `30 * scale` 周围,因为 `dbg!` 返回表达式的值的所有权,所以 `width` 字段将获得相同的值,就像我们在那里没有 `dbg!` 调用一样。我们不希望 `dbg!` 拥有 `rect1` 的所有权,所以我们在下一次调用 `dbg!` 时传递一个引用。下面是这个例子的输出结果: ```console -$ cargo run - Compiling rectangles v0.1.0 (file:///projects/rectangles) - Finished dev [unoptimized + debuginfo] target(s) in 0.61s - Running `target/debug/rectangles` -[src/main.rs:10] 30 * scale = 60 -[src/main.rs:14] &rect1 = Rectangle { - width: 60, - height: 50, -} +{{#include ../listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/output.txt}} ``` 我们可以看到第一点输出来自 *src/main.rs* 第 10 行,我们正在调试表达式 `30 * scale`,其结果值是60(为整数实现的 `Debug` 格式化是只打印它们的值)。在 *src/main.rs* 第 14行 的 `dbg!` 调用输出 `&rect1` 的值,即 `Recangle` 结构。这个输出使用了更为易读的 `Debug` 格式。当你试图弄清楚你的代码在做什么时,`dbg!` 宏可能真的很有帮助! diff --git a/src/ch05-03-method-syntax.md b/src/ch05-03-method-syntax.md index c871c00..980c68c 100644 --- a/src/ch05-03-method-syntax.md +++ b/src/ch05-03-method-syntax.md @@ -4,7 +4,7 @@ >
> commit 8a0bb3c96e71927b80fa2286d7a5a5f2547c6aa4 -**方法** 与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。 +**方法**(method)与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。 ### 定义方法 @@ -13,26 +13,7 @@ 文件名: src/main.rs ```rust -#[derive(Debug)] -struct Rectangle { - width: u32, - height: u32, -} - -impl Rectangle { - fn area(&self) -> u32 { - self.width * self.height - } -} - -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - - println!( - "The area of the rectangle is {} square pixels.", - rect1.area() - ); -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-13/src/main.rs}} ``` 示例 5-13:在 `Rectangle` 结构体上定义 `area` 方法 @@ -50,22 +31,7 @@ fn main() { 文件名: src/main.rs ```rust -impl Rectangle { - fn width(&self) -> bool { - self.width > 0 - } -} - -fn main() { - let rect1 = Rectangle { - width: 30, - height: 50, - }; - - if rect1.width() { - println!("The rectangle has a nonzero width; it is {}", rect1.width); - } -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-06-method-field-interaction/src/main.rs:here}} ``` 在这里,我们选择让 `width` 方法在实例的 `width` 字段的值大于 0 时返回 `true`,等于 0 时则返回 `false`:我们可以出于任何目的,在同名的方法中使用同名的字段。在 `main` 中,当我们在 `rect1.width` 后面加上括号时。Rust 知道我们指的是方法 `width`。当我们不使用圆括号时,Rust 知道我们指的是字段 `width`。 @@ -80,7 +46,6 @@ fn main() { > > 它是这样工作的:当使用 `object.something()` 调用方法时,Rust 会自动为 `object` 添加 `&`、`&mut` 或 `*` 以便使 `object` 与方法签名匹配。也就是说,这些代码是等价的: > -> > ```rust > # #[derive(Debug,Copy,Clone)] > # struct Point { @@ -111,14 +76,7 @@ fn main() { 文件名: src/main.rs ```rust,ignore -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - let rect2 = Rectangle { width: 10, height: 40 }; - let rect3 = Rectangle { width: 60, height: 45 }; - - println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); - println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-14/src/main.rs}} ``` 示例 5-14:使用还未实现的 `can_hold` 方法 @@ -135,21 +93,7 @@ Can rect1 hold rect3? false 文件名: src/main.rs ```rust -# #[derive(Debug)] -# struct Rectangle { -# width: u32, -# height: u32, -# } -# -impl Rectangle { - fn area(&self) -> u32 { - self.width * self.height - } - - fn can_hold(&self, other: &Rectangle) -> bool { - self.width > other.width && self.height > other.height - } -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-15/src/main.rs:here}} ``` 示例 5-15:在 `Rectangle` 上实现 `can_hold` 方法,它获取另一个 `Rectangle` 实例作为参数 @@ -165,17 +109,7 @@ impl Rectangle { 文件名: src/main.rs ```rust -# #[derive(Debug)] -# struct Rectangle { -# width: u32, -# height: u32, -# } -# -impl Rectangle { - fn square(size: u32) -> Rectangle { - Rectangle { width: size, height: size } - } -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}} ``` 使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个方法位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间。第七章会讲到模块。 @@ -185,23 +119,7 @@ impl Rectangle { 每个结构体都允许拥有多个 `impl` 块。例如,示例 5-16 中的代码等同于示例 5-15,但每个方法有其自己的 `impl` 块。 ```rust -# #[derive(Debug)] -# struct Rectangle { -# width: u32, -# height: u32, -# } -# -impl Rectangle { - fn area(&self) -> u32 { - self.width * self.height - } -} - -impl Rectangle { - fn can_hold(&self, other: &Rectangle) -> bool { - self.width > other.width && self.height > other.height - } -} +{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-16/src/main.rs:here}} ``` 示例 5-16:使用多个 `impl` 块重写示例 5-15