From fb1d1cd22b7129fca732c60a8c69dfb4c4039875 Mon Sep 17 00:00:00 2001 From: yang yue Date: Tue, 21 Feb 2017 16:02:19 +0800 Subject: [PATCH] wip --- docs/ch01-00-introduction.html | 2 +- docs/ch01-01-installation.html | 2 +- docs/ch01-02-hello-world.html | 2 +- docs/ch02-00-guessing-game-tutorial.html | 2 +- docs/ch03-00-common-programming-concepts.html | 2 +- docs/ch03-01-variables-and-mutability.html | 2 +- docs/ch03-02-data-types.html | 2 +- docs/ch03-03-how-functions-work.html | 2 +- docs/ch03-04-comments.html | 2 +- docs/ch03-05-control-flow.html | 2 +- docs/ch04-00-understanding-ownership.html | 2 +- docs/ch04-01-what-is-ownership.html | 2 +- docs/ch04-02-references-and-borrowing.html | 2 +- docs/ch04-03-slices.html | 2 +- docs/ch05-00-structs.html | 2 +- docs/ch05-01-method-syntax.html | 2 +- docs/ch06-00-enums.html | 2 +- docs/ch06-01-defining-an-enum.html | 2 +- docs/ch06-02-match.html | 2 +- docs/ch06-03-if-let.html | 2 +- docs/ch07-00-modules.html | 18 +- docs/ch07-01-mod-and-the-filesystem.html | 250 +++++++++- ...07-02-controlling-visibility-with-pub.html | 168 ++++++- docs/ch07-03-importing-names-with-use.html | 10 +- docs/index.html | 2 +- docs/print.html | 432 +++++++++++++++++- src/SUMMARY.md | 2 +- src/ch07-00-modules.md | 15 + src/ch07-01-mod-and-the-filesystem.md | 356 +++++++++++++++ ...ch07-02-controlling-visibility-with-pub.md | 230 ++++++++++ src/ch07-03-importing-names-with-use.md | 6 + 31 files changed, 1498 insertions(+), 31 deletions(-) diff --git a/docs/ch01-00-introduction.html b/docs/ch01-00-introduction.html index b4361da..2a2bff6 100644 --- a/docs/ch01-00-introduction.html +++ b/docs/ch01-00-introduction.html @@ -47,7 +47,7 @@
diff --git a/docs/ch01-01-installation.html b/docs/ch01-01-installation.html index 06ca6c6..c2a54ae 100644 --- a/docs/ch01-01-installation.html +++ b/docs/ch01-01-installation.html @@ -47,7 +47,7 @@
diff --git a/docs/ch01-02-hello-world.html b/docs/ch01-02-hello-world.html index c64b5df..ff321a9 100644 --- a/docs/ch01-02-hello-world.html +++ b/docs/ch01-02-hello-world.html @@ -47,7 +47,7 @@
diff --git a/docs/ch02-00-guessing-game-tutorial.html b/docs/ch02-00-guessing-game-tutorial.html index 0efe91e..5f6cde5 100644 --- a/docs/ch02-00-guessing-game-tutorial.html +++ b/docs/ch02-00-guessing-game-tutorial.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-00-common-programming-concepts.html b/docs/ch03-00-common-programming-concepts.html index e924aee..690f15c 100644 --- a/docs/ch03-00-common-programming-concepts.html +++ b/docs/ch03-00-common-programming-concepts.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-01-variables-and-mutability.html b/docs/ch03-01-variables-and-mutability.html index c129bfd..c78647f 100644 --- a/docs/ch03-01-variables-and-mutability.html +++ b/docs/ch03-01-variables-and-mutability.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-02-data-types.html b/docs/ch03-02-data-types.html index c907434..92e34dc 100644 --- a/docs/ch03-02-data-types.html +++ b/docs/ch03-02-data-types.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-03-how-functions-work.html b/docs/ch03-03-how-functions-work.html index a5bb322..64df1f8 100644 --- a/docs/ch03-03-how-functions-work.html +++ b/docs/ch03-03-how-functions-work.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-04-comments.html b/docs/ch03-04-comments.html index 6d42e98..1ebe74c 100644 --- a/docs/ch03-04-comments.html +++ b/docs/ch03-04-comments.html @@ -47,7 +47,7 @@
diff --git a/docs/ch03-05-control-flow.html b/docs/ch03-05-control-flow.html index 90ba05e..b1567f1 100644 --- a/docs/ch03-05-control-flow.html +++ b/docs/ch03-05-control-flow.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-00-understanding-ownership.html b/docs/ch04-00-understanding-ownership.html index e6f9263..858257f 100644 --- a/docs/ch04-00-understanding-ownership.html +++ b/docs/ch04-00-understanding-ownership.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-01-what-is-ownership.html b/docs/ch04-01-what-is-ownership.html index 499fee9..bdd9f33 100644 --- a/docs/ch04-01-what-is-ownership.html +++ b/docs/ch04-01-what-is-ownership.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-02-references-and-borrowing.html b/docs/ch04-02-references-and-borrowing.html index 0becbfd..8b45cc6 100644 --- a/docs/ch04-02-references-and-borrowing.html +++ b/docs/ch04-02-references-and-borrowing.html @@ -47,7 +47,7 @@
diff --git a/docs/ch04-03-slices.html b/docs/ch04-03-slices.html index 9df2aaf..3e501c4 100644 --- a/docs/ch04-03-slices.html +++ b/docs/ch04-03-slices.html @@ -47,7 +47,7 @@
diff --git a/docs/ch05-00-structs.html b/docs/ch05-00-structs.html index 9ba2de5..cc1b858 100644 --- a/docs/ch05-00-structs.html +++ b/docs/ch05-00-structs.html @@ -47,7 +47,7 @@
diff --git a/docs/ch05-01-method-syntax.html b/docs/ch05-01-method-syntax.html index fbcc767..d6e509b 100644 --- a/docs/ch05-01-method-syntax.html +++ b/docs/ch05-01-method-syntax.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-00-enums.html b/docs/ch06-00-enums.html index 19dd0ea..890dde4 100644 --- a/docs/ch06-00-enums.html +++ b/docs/ch06-00-enums.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-01-defining-an-enum.html b/docs/ch06-01-defining-an-enum.html index 845d466..05bb1d8 100644 --- a/docs/ch06-01-defining-an-enum.html +++ b/docs/ch06-01-defining-an-enum.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-02-match.html b/docs/ch06-02-match.html index 91b393b..2b3b06a 100644 --- a/docs/ch06-02-match.html +++ b/docs/ch06-02-match.html @@ -47,7 +47,7 @@
diff --git a/docs/ch06-03-if-let.html b/docs/ch06-03-if-let.html index 5849177..5e32952 100644 --- a/docs/ch06-03-if-let.html +++ b/docs/ch06-03-if-let.html @@ -47,7 +47,7 @@
diff --git a/docs/ch07-00-modules.html b/docs/ch07-00-modules.html index 3a23abf..8f73d7a 100644 --- a/docs/ch07-00-modules.html +++ b/docs/ch07-00-modules.html @@ -47,7 +47,7 @@
@@ -67,7 +67,21 @@
- +

模块

+
+

ch07-00-modules.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+

在你刚开始编写 Rust 程序时,代码可能仅仅位于main函数里。随着代码数量的增长,最终你会将功能移动到其他函数中,为了复用也为了更好的组织。通过将代码分隔成更小的块,每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢?Rust 有一个模块系统来处理编写可复用代码同时保持代码组织度的问题。

+

就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。模块module)是一个包含函数或类型定义的命名空间,你可以选择这些定义是能(公有)还是不能(私有)在其模块外可见。这是一个模块如何工作的概括:

+
    +
  • 使用mod关键字声明模块
  • +
  • 默认所有内容都是私有的(包括模块自身)。可以使用pub关键字将其变成公有并在其命名空间外可见。
  • +
  • use关键字允许引入模块、或模块中的定义到作用域中以便于引用他们。
  • +
+

我们会逐一了解这每一部分并学习如何将他们结合在一起。

+
diff --git a/docs/ch07-01-mod-and-the-filesystem.html b/docs/ch07-01-mod-and-the-filesystem.html index 8742049..b341fe2 100644 --- a/docs/ch07-01-mod-and-the-filesystem.html +++ b/docs/ch07-01-mod-and-the-filesystem.html @@ -47,7 +47,7 @@
@@ -67,7 +67,253 @@
- +

mod和文件系统

+
+

ch07-01-mod-and-the-filesystem.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+

我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过我们不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章我们见过的rand就是这样的 crate。

+

我们将创建一个提供一些通用网络功能的项目的骨架结构;我们将专注于模块和函数的组织,而不担心函数体中的具体代码。这个项目叫做communicator。Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入--bin参数则项目将会是一个库:

+
$ cargo new communicator
+$ cd communicator
+
+

注意 Cargo 生成了 src/lib.rs 而不是 src/main.rs。在 src/lib.rs 中我们会找到这些:

+

Filename: src/lib.rs

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+    }
+}
+
+

Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用--bin参数那样创建一个“Hello, world!”二进制项目。稍后一点会介绍#[]mod tests语法,目前只需确保他们位于 src/lib.rs 中。

+

因为没有 src/main.rs 文件,所以没有可供 Cargo 的cargo run执行的东西。因此,我们将使用cargo build命令只是编译库 crate 的代码。

+

我们将学习根据编写代码的意图来选择不同的织库项目代码组织来适应多种场景。

+

模块定义

+

对于communicator网络库,首先我们要定义一个叫做network的模块,它包含一个叫做connect的函数定义。Rust 中所有模块的定义以关键字mod开始。在 src/lib.rs 文件的开头在测试代码的上面增加这些代码:

+

Filename: src/lib.rs

+
mod network {
+    fn connect() {
+    }
+}
+
+

mod关键字的后面是模块的名字,network,接着是位于大括号中的代码块。代码块中的一切都位于network命名空间中。在这个例子中,只有一个函数,connect。如果想要在network模块外面的代码中调用这个函数,需要指定模块名并使用命名空间语法::,像这样:network::connect(),而不是只是connect()

+

也可以在 src/lib.rs 文件中同时存在多个模块。例如,再拥有一个client模块,它也有一个叫做connect的函数,如列表 7-1 中所示那样增加这个模块:

+
+Filename: src/lib.rs +
mod network {
+    fn connect() {
+    }
+}
+
+mod client {
+    fn connect() {
+    }
+}
+
+
+

Listing 7-1: The network module and the client module defined side-by-side +in src/lib.rs

+
+
+

现在我们有了network::connect函数和client::connect函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突因为他们位于不同的模块。

+

虽然在这个例子中,我们构建了一个库,但是 src/lib.rs 并没有什么特殊意义。也可以在 src/main.rs 中使用子模块。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,client模块和它的函数connect可能放在network命名空间里显得更有道理,如列表 7-2 所示:

+
+Filename: src/lib.rs +
mod network {
+    fn connect() {
+    }
+
+    mod client {
+        fn connect() {
+        }
+    }
+}
+
+
+

Listing 7-2: Moving the client module inside of the network module

+
+
+

src/lib.rs 文件中,将现有的mod networkmod client的定义替换为client模块作为network的一个内部模块。现在我们有了network::connectnetwork::client::connect函数:又一次,这两个connect函数也不相冲突,因为他们在不同的命名空间中。

+

这样,模块之间形成了一个层次结构。src/lib.rs 的内容位于最顶层,而其子模块位于较低的层次。这是列表 7-1 中的例子以这种方式考虑的组织结构:

+
communicator
+ ├── network
+ └── client
+
+

而这是列表 7-2 中例子的的结构:

+
communicator
+ └── network
+     └── client
+
+

可以看到列表 7-2 中,clientnetwork的子模块,而不是它的同级模块。更为负责的项目可以有很多的模块,所以他们需要符合逻辑地组合在一起以便记录他们。在项目中“符合逻辑”的意义全凭你得理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块将是你会喜欢的结构。

+

将模块移动到其他文件

+

位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不是所有的内容都落到 src/lib.rs 中了。作为例子,我们将从列表 7-3 中的代码开始:

+
+Filename: src/lib.rs +
mod client {
+    fn connect() {
+    }
+}
+
+mod network {
+    fn connect() {
+    }
+
+    mod server {
+        fn connect() {
+        }
+    }
+}
+
+
+

Listing 7-3: Three modules, client, network, and network::server, all +defined in src/lib.rs

+
+
+

这是模块层次结构:

+
communicator
+ ├── client
+ └── network
+     └── server
+
+

这里我们仍然定义client模块,不过去掉了大括号和client模块中的定义并替换为一个分号,这使得 Rust 知道去其他地方寻找模块中定义的代码。

+

那么现在需要创建对应模块名的外部文件。在 src/ 目录创建一个 client.rs 文件,接着打开它并输入如下内容,它是上一步client模块中被去掉的connect函数:

+

Filename: src/client.rs

+
fn connect() {
+}
+
+

注意这个文件中并不需要一个mod声明;因为已经在 src/lib.rs 中已经使用mod声明了client模块。这个文件仅仅提供client模块的内容。如果在这里加上一个mod client,那么就等于给client模块增加了一个叫做client的子模块!

+

Rust 默认只知道 src/lib.rs 中的内容。如果想要对项目加入更多文件,我们需要在 src/lib.rs 中告诉 Rust 去寻找其他文件;这就是为什么mod client需要被定义在 src/lib.rs 而不是在 src/client.rs

+

现在,一切应该能成功编译,虽然会有一些警告。记住使用cargo build而不是cargo run因为这是一个库 crate 而不是二进制 crate:

+
$ cargo build
+   Compiling communicator v0.1.0 (file:///projects/communicator)
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/client.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/lib.rs:4:5
+  |
+4 |     fn connect() {
+  |     ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/lib.rs:8:9
+  |
+8 |         fn connect() {
+  |         ^
+
+

这些警告提醒我们有从未被使用的函数。目前不用担心这些警告;在本章的后面会解决他们。好消息是,他们仅仅是警告;我们的项目能够被成功编译。

+

下面使用相同的模式将network模块提取到它自己的文件中。删除 src/lib.rsnetwork模块的内容并在声明后加上一个分号,像这样:

+

Filename: src/lib.rs

+
mod client;
+
+mod network;
+
+

接着新建 src/network.rs 文件并输入如下内容:

+

Filename: src/network.rs

+
fn connect() {
+}
+
+mod server {
+    fn connect() {
+    }
+}
+
+

注意这个模块文件中我们也使用了一个mod声明;这是因为我们希望server成为network的一个子模块。

+

现在再次运行cargo build。成功!不过我们还需要再提取出另一个模块:server。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 src/network.rs 的第一个修改是用mod server;替换server模块的内容:

+

Filename: src/network.rs

+
fn connect() {
+}
+
+mod server;
+
+

接着创建 src/server.rs 文件并输入需要提取的server模块的内容:

+

Filename: src/server.rs

+
fn connect() {
+}
+
+

当尝试运行cargo build时,会出现如列表 7-4 中所示的错误:

+
+
$ cargo build
+   Compiling communicator v0.1.0 (file:///projects/communicator)
+error: cannot declare a new module at this location
+ --> src/network.rs:4:5
+  |
+4 | mod server;
+  |     ^^^^^^
+  |
+note: maybe move this module `network` to its own directory via `network/mod.rs`
+ --> src/network.rs:4:5
+  |
+4 | mod server;
+  |     ^^^^^^
+note: ... or maybe `use` the module `server` instead of possibly redeclaring it
+ --> src/network.rs:4:5
+  |
+4 | mod server;
+  |     ^^^^^^
+
+
+

Listing 7-4: Error when trying to extract the server submodule into +src/server.rs

+
+
+

这个错误说明“不能在这个位置新声明一个模块”并指出 src/network.rs 中的mod server;这一行。看来 src/network.rssrc/lib.rs 在某些方面是不同的;让我们继续阅读以理解这是为什么。

+

列表 7-4 中间的记录事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作:

+
note: maybe move this module `network` to its own directory via `network/mod.rs`
+
+

我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名的文件的模式:

+
    +
  1. 新建一个叫做 network目录,这是父模块的名字
  2. +
  3. src/network.rs 移动到新建的 network 目录中并重命名,现在它是 src/network/mod.rs
  4. +
  5. 将子模块文件 src/server.rs 移动到 network 目录中
  6. +
+

如下是执行这些步骤的命令:

+
$ mkdir src/network
+$ mv src/network.rs src/network/mod.rs
+$ mv src/server.rs src/network
+
+

现在如果运行cargo build的话将顺利编译(虽然仍有警告)。现在模块的布局看起来仍然与列表 7-3 中所有代码都在 src/lib.rs 中时完全一样:

+
communicator
+ ├── client
+ └── network
+     └── server
+
+

对应的文件布局现在看起来像这样:

+
├── src
+│   ├── client.rs
+│   ├── lib.rs
+│   └── network
+│       ├── mod.rs
+│       └── server.rs
+
+

那么,当我们想要提取network::server模块时,为什么也必须将 src/network.rs 文件改名成 src/network/mod.rs 文件呢,还有为什么要将network::server的代码放入 network 目录的 src/network/server.rs 文件中,而不能将network::server模块提取到 src/server.rs 中呢?原因是如果 server.rs 文件在 src 目录中那么 Rust 就不能知道server应当是network的子模块。为了更清除的说明为什么 Rust 不知道,让我们考虑一下有着如下层级的另一个例子,它的所有定义都位于 src/lib.rs 中:

+
communicator
+ ├── client
+ └── network
+     └── client
+
+

在这个例子中,仍然有这三个模块,clientnetworknetwork::client。如果按照与上面最开始将模块提取到文件中相同的步骤来操作,对于client模块会创建 src/client.rs。对于network模块,会创建 src/network.rs。但是接下来不能将network::client模块提取到 src/client.rs 文件中,因为它已经存在了,对应顶层的client模块!如果将clientnetwork::client的代码都放入 src/client.rs 文件,Rust 将无从可知这些代码是属于client还是network::client的。

+

因此,一旦想要将network模块的子模块network::client提取到一个文件中,需要为network模块新建一个目录替代 src/network.rs 文件。接着network模块的代码将进入 src/network/mod.rs 文件,而子模块network::client将拥有其自己的文件 src/network/client.rs。现在顶层的 src/client.rs 中的代码毫无疑问的都属于client模块。

+

模块文件系统的规则

+

与文件系统相关的模块规则总结如下:

+
    +
  • 如果一个叫做foo的模块没有子模块,应该将foo的声明放入叫做 foo.rs 的文件中。
  • +
  • 如果一个叫做foo的模块有子模块,应该将foo的声明放入叫做 foo/mod.rs 的文件中。
  • +
+

这些规则适用于递归(嵌套),所以如果foo模块有一个子模块barbar没有子模块,则 src 目录中应该有如下文件:

+
├── foo
+│   ├── bar.rs (contains the declarations in `foo::bar`)
+│   └── mod.rs (contains the declarations in `foo`, including `mod bar`)
+
+

模块自身则应该使用mod关键字定义于父模块的文件中。

+

接下来,我们讨论一下pub关键字,并除掉那些警告!

+
diff --git a/docs/ch07-02-controlling-visibility-with-pub.html b/docs/ch07-02-controlling-visibility-with-pub.html index 2ddd01e..6fb98b4 100644 --- a/docs/ch07-02-controlling-visibility-with-pub.html +++ b/docs/ch07-02-controlling-visibility-with-pub.html @@ -47,7 +47,7 @@
@@ -67,7 +67,171 @@
- +

使用pub控制可见性

+
+

ch07-02-controlling-visibility-with-pub.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+

我们通过将networknetwork::server的代码分别移动到 src/network/mod.rssrc/network/server.rs 文件中解决了列表 7-4 中出现的错误信息。现在,cargo build能够构建我们的项目,不过仍然有一些警告信息,表示client::connectnetwork::connectnetwork::server::connect函数没有被使用:

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+src/client.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/mod.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被用户使用,而不一定要被项目自身使用,所以不应该担心这些函数是未被使用的。创建他们的意义就在于被另一个项目而不是被自己使用。

+

为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个connect库,从外部调用他们。为此,通过创建一个包含这些代码的 src/main.rs 文件,在与库 crate 相同的目录创建一个二进制 crate:

+

Filename: src/main.rs

+
extern crate communicator;
+
+fn main() {
+    communicator::client::connect();
+}
+
+

使用extern crate指令将communicator库 crate 引入到作用域,因为事实上我们的包包含两个 crate。Cargo 认为 src/main.rs 是一个二进制 crate 的根文件,与现存的以 src/lib.rs 为根文件的库 crate 相区分。这个模式在可执行项目中非常常见:大部分功能位于库 crate 中,而二进制 crate 使用这个库 crate。通过这种方式,其他程序也可以使用这个库 crate,这是一个很好的关注分离(separation of concerns)。

+

从一个外部 crate 的视角观察communicator库的内部,我们创建的所有模块都位于一个与 crate 同名的模块内部,communicator。这个顶层的模块被称为 crate 的根模块root module)。

+

另外注意到即便在项目的子模块中使用外部 crate,extern crate也应该位于根模块(也就是 src/main.rssrc/lib.rs)。接着,在子模块中,我们就可以像顶层模块那样引用外部 crate 中的项了。

+

我们的二进制 crate 如今正好调用了库中client模块的connect函数。然而,执行cargo build会在之前的警告之后出现一个错误:

+
error: module `client` is private
+ --> src/main.rs:4:5
+  |
+4 |     communicator::client::connect();
+  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+

啊哈!这告诉了我们client模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到公有私有的概念。Rust 所有代码的默认状态是私有的:除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数,因为程序自身是唯一允许使用这个函数的代码,Rust 会警告说函数未被使用。

+

一旦我们指定一个像client::connect的函数为公有,不光二进制 crate 中的函数调用会被允许,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了我们意在使函数在我们程序的外部被使用。现在这个可能的理论上的外部可用性使 Rust 认为这个函数“已经被使用”。因此。当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告某项未被使用。

+

标记函数为公有

+

为了告诉 Rust 某项为公有,在想要标记为公有的项的声明开头加上pub关键字。现在我们将致力于修复client::connect未被使用的警告,以及二进制 crate 中“模块client是私有的”的错误。像这样修改 src/lib.rs 使client模块公有:

+

Filename: src/lib.rs

+
pub mod client;
+
+mod network;
+
+

pub写在mod之前。再次尝试构建:

+
<warnings>
+error: function `connect` is private
+ --> src/main.rs:4:5
+  |
+4 |     communicator::client::connect();
+  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+

非常好!另一个不同的错误!好的,不同的错误信息是值得庆祝的(可能是程序员被黑的最惨的一次)。新错误表明“函数connect是私有的”,那么让我们修改 src/client.rsclient::connect也设为公有:

+

Filename: src/client.rs

+
pub fn connect() {
+}
+
+

再再一次运行cargo build

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/mod.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

编译通过了,关于client::connect未被使用的警告消失了!

+

未被使用的代码并不总是意味着他们需要被设为公有的:如果你希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除他们。这也可能是警告你出 bug,如果你刚刚不小心删除了库中所有这个函数的调用。

+

当然我们的情况是,确实希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为pub并去掉剩余的警告。修改 src/network/mod.rs 为:

+

Filename: src/network/mod.rs

+
pub fn connect() {
+}
+
+mod server;
+
+

并编译:

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/mod.rs:1:1
+  |
+1 | pub fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

恩,虽然将network::connect设为pub了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的network模块却不是公有的。这回我们是自内向外修改库文件的,而client::connect的时候是自外向内修改的。我们需要修改 src/lib.rsnetwork 也是公有的:

+

Filename: src/lib.rs

+
pub mod client;
+
+pub mod network;
+
+

现在再编译的话,那个警告就消失了:

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

只剩一个警告了!尝试自食其力修改它吧!

+

私有性规则

+

总的来说,有如下项的可见性规则:

+
    +
  1. 如果一个项是公有的,它能被任何父模块访问
  2. +
  3. 如果一个项是私有的,它只能被当前模块或其子模块访问
  4. +
+

私有性示例

+

让我们看看更多例子作为练习。创建一个新的库项目并在新项目的 src/lib.rs 输入列表 7-5 中的代码:

+
+Filename: src/lib.rs +
mod outermost {
+    pub fn middle_function() {}
+
+    fn middle_secret_function() {}
+
+    mod inside {
+        pub fn inner_function() {}
+
+        fn secret_function() {}
+    }
+}
+
+fn try_me() {
+    outermost::middle_function();
+    outermost::middle_secret_function();
+    outermost::inside::inner_function();
+    outermost::inside::secret_function();
+}
+
+
+

Listing 7-5: Examples of private and public functions, some of which are +incorrect

+
+
+

在尝试编译这些代码之前,猜测一下try_me函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论!

+

检查错误

+

try_me函数位于项目的根模块。叫做outermost的模块是私有的,不过第二条私有性规则说明try_me函数允许访问outermost模块,因为outermost位于当前(根)模块,try_me也是。

+

outermost::middle_function的调用是正确的。因为middle_function是公有的,而try_me通过其父模块访问middle_functionoutermost。根据上一段的规则我们可以确定这个模块是可访问的。

+

outermost::middle_secret_function的调用会造成一个编译错误。middle_secret_function是私有的,所以第二条(私有性)规则生效了。根模块既不是middle_secret_function的当前模块(outermost是),也不是middle_secret_function当前模块的子模块。

+

叫做inside的模块是私有的且没有子模块,所以它只能被当前模块访问,outermost。这意味着try_me函数不允许调用outermost::inside::inner_functionoutermost::inside::secret_function任何一个。

+

修改错误

+

这里有一些尝试修复错误的代码修改意见。在你尝试他们之前,猜测一下他们哪个能修复错误,接着编译查看你是否猜对了,并结合私有性规则理解为什么。

+
    +
  • 如果inside模块是公有的?
  • +
  • 如果outermost是公有的而inside是私有的?
  • +
  • 如果在inner_function函数体中调用::outermost::middle_secret_function()?(开头的两个冒号意味着从根模块开始引用模块。)
  • +
+

请随意设计更多的实验并尝试理解他们!

+

接下来,让我们讨论一下使用use关键字来将项引入作用域。

+
diff --git a/docs/ch07-03-importing-names-with-use.html b/docs/ch07-03-importing-names-with-use.html index 6135efd..2118ab4 100644 --- a/docs/ch07-03-importing-names-with-use.html +++ b/docs/ch07-03-importing-names-with-use.html @@ -47,7 +47,7 @@
@@ -67,7 +67,13 @@
- +

使用use导入命名空间

+
+

ch07-03-importing-names-with-use.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+
diff --git a/docs/index.html b/docs/index.html index 55e6998..f04936f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -46,7 +46,7 @@
diff --git a/docs/print.html b/docs/print.html index 2808291..1427cda 100644 --- a/docs/print.html +++ b/docs/print.html @@ -47,7 +47,7 @@
@@ -3104,6 +3104,436 @@ if let Coin::Quarter(state) = coin {

现在我们涉及到了如何使用枚举来创建有一系列可列举值的自定义类型。我们也展示了标准库的Option<T>类型是如何帮助你利用类型系统来避免出错。当枚举值包含数据时,你可以根据需要处理多少情况来选择使用matchif let来获取并使用这些值。

你的 Rust 程序现在能够使用结构体和枚举在自己的作用域内表现其内容了。在你的 API 中使用自定义类型保证了类型安全:编译器会确保你的函数只会得到它期望的类型的值。

为了向你的用户提供一个组织良好的 API,它使用直观且只向用户暴露他们确实需要的部分,那么让我们转向 Rust 的模块系统吧。

+

模块

+
+

ch07-00-modules.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+

在你刚开始编写 Rust 程序时,代码可能仅仅位于main函数里。随着代码数量的增长,最终你会将功能移动到其他函数中,为了复用也为了更好的组织。通过将代码分隔成更小的块,每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢?Rust 有一个模块系统来处理编写可复用代码同时保持代码组织度的问题。

+

就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。模块module)是一个包含函数或类型定义的命名空间,你可以选择这些定义是能(公有)还是不能(私有)在其模块外可见。这是一个模块如何工作的概括:

+
    +
  • 使用mod关键字声明模块
  • +
  • 默认所有内容都是私有的(包括模块自身)。可以使用pub关键字将其变成公有并在其命名空间外可见。
  • +
  • use关键字允许引入模块、或模块中的定义到作用域中以便于引用他们。
  • +
+

我们会逐一了解这每一部分并学习如何将他们结合在一起。

+

mod和文件系统

+
+

ch07-01-mod-and-the-filesystem.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+

我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过我们不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章我们见过的rand就是这样的 crate。

+

我们将创建一个提供一些通用网络功能的项目的骨架结构;我们将专注于模块和函数的组织,而不担心函数体中的具体代码。这个项目叫做communicator。Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入--bin参数则项目将会是一个库:

+
$ cargo new communicator
+$ cd communicator
+
+

注意 Cargo 生成了 src/lib.rs 而不是 src/main.rs。在 src/lib.rs 中我们会找到这些:

+

Filename: src/lib.rs

+
#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+    }
+}
+
+

Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用--bin参数那样创建一个“Hello, world!”二进制项目。稍后一点会介绍#[]mod tests语法,目前只需确保他们位于 src/lib.rs 中。

+

因为没有 src/main.rs 文件,所以没有可供 Cargo 的cargo run执行的东西。因此,我们将使用cargo build命令只是编译库 crate 的代码。

+

我们将学习根据编写代码的意图来选择不同的织库项目代码组织来适应多种场景。

+

模块定义

+

对于communicator网络库,首先我们要定义一个叫做network的模块,它包含一个叫做connect的函数定义。Rust 中所有模块的定义以关键字mod开始。在 src/lib.rs 文件的开头在测试代码的上面增加这些代码:

+

Filename: src/lib.rs

+
mod network {
+    fn connect() {
+    }
+}
+
+

mod关键字的后面是模块的名字,network,接着是位于大括号中的代码块。代码块中的一切都位于network命名空间中。在这个例子中,只有一个函数,connect。如果想要在network模块外面的代码中调用这个函数,需要指定模块名并使用命名空间语法::,像这样:network::connect(),而不是只是connect()

+

也可以在 src/lib.rs 文件中同时存在多个模块。例如,再拥有一个client模块,它也有一个叫做connect的函数,如列表 7-1 中所示那样增加这个模块:

+
+Filename: src/lib.rs +
mod network {
+    fn connect() {
+    }
+}
+
+mod client {
+    fn connect() {
+    }
+}
+
+
+

Listing 7-1: The network module and the client module defined side-by-side +in src/lib.rs

+
+
+

现在我们有了network::connect函数和client::connect函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突因为他们位于不同的模块。

+

虽然在这个例子中,我们构建了一个库,但是 src/lib.rs 并没有什么特殊意义。也可以在 src/main.rs 中使用子模块。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,client模块和它的函数connect可能放在network命名空间里显得更有道理,如列表 7-2 所示:

+
+Filename: src/lib.rs +
mod network {
+    fn connect() {
+    }
+
+    mod client {
+        fn connect() {
+        }
+    }
+}
+
+
+

Listing 7-2: Moving the client module inside of the network module

+
+
+

src/lib.rs 文件中,将现有的mod networkmod client的定义替换为client模块作为network的一个内部模块。现在我们有了network::connectnetwork::client::connect函数:又一次,这两个connect函数也不相冲突,因为他们在不同的命名空间中。

+

这样,模块之间形成了一个层次结构。src/lib.rs 的内容位于最顶层,而其子模块位于较低的层次。这是列表 7-1 中的例子以这种方式考虑的组织结构:

+
communicator
+ ├── network
+ └── client
+
+

而这是列表 7-2 中例子的的结构:

+
communicator
+ └── network
+     └── client
+
+

可以看到列表 7-2 中,clientnetwork的子模块,而不是它的同级模块。更为负责的项目可以有很多的模块,所以他们需要符合逻辑地组合在一起以便记录他们。在项目中“符合逻辑”的意义全凭你得理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块将是你会喜欢的结构。

+

将模块移动到其他文件

+

位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不是所有的内容都落到 src/lib.rs 中了。作为例子,我们将从列表 7-3 中的代码开始:

+
+Filename: src/lib.rs +
mod client {
+    fn connect() {
+    }
+}
+
+mod network {
+    fn connect() {
+    }
+
+    mod server {
+        fn connect() {
+        }
+    }
+}
+
+
+

Listing 7-3: Three modules, client, network, and network::server, all +defined in src/lib.rs

+
+
+

这是模块层次结构:

+
communicator
+ ├── client
+ └── network
+     └── server
+
+

这里我们仍然定义client模块,不过去掉了大括号和client模块中的定义并替换为一个分号,这使得 Rust 知道去其他地方寻找模块中定义的代码。

+

那么现在需要创建对应模块名的外部文件。在 src/ 目录创建一个 client.rs 文件,接着打开它并输入如下内容,它是上一步client模块中被去掉的connect函数:

+

Filename: src/client.rs

+
fn connect() {
+}
+
+

注意这个文件中并不需要一个mod声明;因为已经在 src/lib.rs 中已经使用mod声明了client模块。这个文件仅仅提供client模块的内容。如果在这里加上一个mod client,那么就等于给client模块增加了一个叫做client的子模块!

+

Rust 默认只知道 src/lib.rs 中的内容。如果想要对项目加入更多文件,我们需要在 src/lib.rs 中告诉 Rust 去寻找其他文件;这就是为什么mod client需要被定义在 src/lib.rs 而不是在 src/client.rs

+

现在,一切应该能成功编译,虽然会有一些警告。记住使用cargo build而不是cargo run因为这是一个库 crate 而不是二进制 crate:

+
$ cargo build
+   Compiling communicator v0.1.0 (file:///projects/communicator)
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/client.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/lib.rs:4:5
+  |
+4 |     fn connect() {
+  |     ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/lib.rs:8:9
+  |
+8 |         fn connect() {
+  |         ^
+
+

这些警告提醒我们有从未被使用的函数。目前不用担心这些警告;在本章的后面会解决他们。好消息是,他们仅仅是警告;我们的项目能够被成功编译。

+

下面使用相同的模式将network模块提取到它自己的文件中。删除 src/lib.rsnetwork模块的内容并在声明后加上一个分号,像这样:

+

Filename: src/lib.rs

+
mod client;
+
+mod network;
+
+

接着新建 src/network.rs 文件并输入如下内容:

+

Filename: src/network.rs

+
fn connect() {
+}
+
+mod server {
+    fn connect() {
+    }
+}
+
+

注意这个模块文件中我们也使用了一个mod声明;这是因为我们希望server成为network的一个子模块。

+

现在再次运行cargo build。成功!不过我们还需要再提取出另一个模块:server。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 src/network.rs 的第一个修改是用mod server;替换server模块的内容:

+

Filename: src/network.rs

+
fn connect() {
+}
+
+mod server;
+
+

接着创建 src/server.rs 文件并输入需要提取的server模块的内容:

+

Filename: src/server.rs

+
fn connect() {
+}
+
+

当尝试运行cargo build时,会出现如列表 7-4 中所示的错误:

+
+
$ cargo build
+   Compiling communicator v0.1.0 (file:///projects/communicator)
+error: cannot declare a new module at this location
+ --> src/network.rs:4:5
+  |
+4 | mod server;
+  |     ^^^^^^
+  |
+note: maybe move this module `network` to its own directory via `network/mod.rs`
+ --> src/network.rs:4:5
+  |
+4 | mod server;
+  |     ^^^^^^
+note: ... or maybe `use` the module `server` instead of possibly redeclaring it
+ --> src/network.rs:4:5
+  |
+4 | mod server;
+  |     ^^^^^^
+
+
+

Listing 7-4: Error when trying to extract the server submodule into +src/server.rs

+
+
+

这个错误说明“不能在这个位置新声明一个模块”并指出 src/network.rs 中的mod server;这一行。看来 src/network.rssrc/lib.rs 在某些方面是不同的;让我们继续阅读以理解这是为什么。

+

列表 7-4 中间的记录事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作:

+
note: maybe move this module `network` to its own directory via `network/mod.rs`
+
+

我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名的文件的模式:

+
    +
  1. 新建一个叫做 network目录,这是父模块的名字
  2. +
  3. src/network.rs 移动到新建的 network 目录中并重命名,现在它是 src/network/mod.rs
  4. +
  5. 将子模块文件 src/server.rs 移动到 network 目录中
  6. +
+

如下是执行这些步骤的命令:

+
$ mkdir src/network
+$ mv src/network.rs src/network/mod.rs
+$ mv src/server.rs src/network
+
+

现在如果运行cargo build的话将顺利编译(虽然仍有警告)。现在模块的布局看起来仍然与列表 7-3 中所有代码都在 src/lib.rs 中时完全一样:

+
communicator
+ ├── client
+ └── network
+     └── server
+
+

对应的文件布局现在看起来像这样:

+
├── src
+│   ├── client.rs
+│   ├── lib.rs
+│   └── network
+│       ├── mod.rs
+│       └── server.rs
+
+

那么,当我们想要提取network::server模块时,为什么也必须将 src/network.rs 文件改名成 src/network/mod.rs 文件呢,还有为什么要将network::server的代码放入 network 目录的 src/network/server.rs 文件中,而不能将network::server模块提取到 src/server.rs 中呢?原因是如果 server.rs 文件在 src 目录中那么 Rust 就不能知道server应当是network的子模块。为了更清除的说明为什么 Rust 不知道,让我们考虑一下有着如下层级的另一个例子,它的所有定义都位于 src/lib.rs 中:

+
communicator
+ ├── client
+ └── network
+     └── client
+
+

在这个例子中,仍然有这三个模块,clientnetworknetwork::client。如果按照与上面最开始将模块提取到文件中相同的步骤来操作,对于client模块会创建 src/client.rs。对于network模块,会创建 src/network.rs。但是接下来不能将network::client模块提取到 src/client.rs 文件中,因为它已经存在了,对应顶层的client模块!如果将clientnetwork::client的代码都放入 src/client.rs 文件,Rust 将无从可知这些代码是属于client还是network::client的。

+

因此,一旦想要将network模块的子模块network::client提取到一个文件中,需要为network模块新建一个目录替代 src/network.rs 文件。接着network模块的代码将进入 src/network/mod.rs 文件,而子模块network::client将拥有其自己的文件 src/network/client.rs。现在顶层的 src/client.rs 中的代码毫无疑问的都属于client模块。

+

模块文件系统的规则

+

与文件系统相关的模块规则总结如下:

+
    +
  • 如果一个叫做foo的模块没有子模块,应该将foo的声明放入叫做 foo.rs 的文件中。
  • +
  • 如果一个叫做foo的模块有子模块,应该将foo的声明放入叫做 foo/mod.rs 的文件中。
  • +
+

这些规则适用于递归(嵌套),所以如果foo模块有一个子模块barbar没有子模块,则 src 目录中应该有如下文件:

+
├── foo
+│   ├── bar.rs (contains the declarations in `foo::bar`)
+│   └── mod.rs (contains the declarations in `foo`, including `mod bar`)
+
+

模块自身则应该使用mod关键字定义于父模块的文件中。

+

接下来,我们讨论一下pub关键字,并除掉那些警告!

+

使用pub控制可见性

+
+

ch07-02-controlling-visibility-with-pub.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
+

我们通过将networknetwork::server的代码分别移动到 src/network/mod.rssrc/network/server.rs 文件中解决了列表 7-4 中出现的错误信息。现在,cargo build能够构建我们的项目,不过仍然有一些警告信息,表示client::connectnetwork::connectnetwork::server::connect函数没有被使用:

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+src/client.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/mod.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被用户使用,而不一定要被项目自身使用,所以不应该担心这些函数是未被使用的。创建他们的意义就在于被另一个项目而不是被自己使用。

+

为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个connect库,从外部调用他们。为此,通过创建一个包含这些代码的 src/main.rs 文件,在与库 crate 相同的目录创建一个二进制 crate:

+

Filename: src/main.rs

+
extern crate communicator;
+
+fn main() {
+    communicator::client::connect();
+}
+
+

使用extern crate指令将communicator库 crate 引入到作用域,因为事实上我们的包包含两个 crate。Cargo 认为 src/main.rs 是一个二进制 crate 的根文件,与现存的以 src/lib.rs 为根文件的库 crate 相区分。这个模式在可执行项目中非常常见:大部分功能位于库 crate 中,而二进制 crate 使用这个库 crate。通过这种方式,其他程序也可以使用这个库 crate,这是一个很好的关注分离(separation of concerns)。

+

从一个外部 crate 的视角观察communicator库的内部,我们创建的所有模块都位于一个与 crate 同名的模块内部,communicator。这个顶层的模块被称为 crate 的根模块root module)。

+

另外注意到即便在项目的子模块中使用外部 crate,extern crate也应该位于根模块(也就是 src/main.rssrc/lib.rs)。接着,在子模块中,我们就可以像顶层模块那样引用外部 crate 中的项了。

+

我们的二进制 crate 如今正好调用了库中client模块的connect函数。然而,执行cargo build会在之前的警告之后出现一个错误:

+
error: module `client` is private
+ --> src/main.rs:4:5
+  |
+4 |     communicator::client::connect();
+  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+

啊哈!这告诉了我们client模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到公有私有的概念。Rust 所有代码的默认状态是私有的:除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数,因为程序自身是唯一允许使用这个函数的代码,Rust 会警告说函数未被使用。

+

一旦我们指定一个像client::connect的函数为公有,不光二进制 crate 中的函数调用会被允许,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了我们意在使函数在我们程序的外部被使用。现在这个可能的理论上的外部可用性使 Rust 认为这个函数“已经被使用”。因此。当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告某项未被使用。

+

标记函数为公有

+

为了告诉 Rust 某项为公有,在想要标记为公有的项的声明开头加上pub关键字。现在我们将致力于修复client::connect未被使用的警告,以及二进制 crate 中“模块client是私有的”的错误。像这样修改 src/lib.rs 使client模块公有:

+

Filename: src/lib.rs

+
pub mod client;
+
+mod network;
+
+

pub写在mod之前。再次尝试构建:

+
<warnings>
+error: function `connect` is private
+ --> src/main.rs:4:5
+  |
+4 |     communicator::client::connect();
+  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+

非常好!另一个不同的错误!好的,不同的错误信息是值得庆祝的(可能是程序员被黑的最惨的一次)。新错误表明“函数connect是私有的”,那么让我们修改 src/client.rsclient::connect也设为公有:

+

Filename: src/client.rs

+
pub fn connect() {
+}
+
+

再再一次运行cargo build

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/mod.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

编译通过了,关于client::connect未被使用的警告消失了!

+

未被使用的代码并不总是意味着他们需要被设为公有的:如果你希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除他们。这也可能是警告你出 bug,如果你刚刚不小心删除了库中所有这个函数的调用。

+

当然我们的情况是,确实希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为pub并去掉剩余的警告。修改 src/network/mod.rs 为:

+

Filename: src/network/mod.rs

+
pub fn connect() {
+}
+
+mod server;
+
+

并编译:

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/mod.rs:1:1
+  |
+1 | pub fn connect() {
+  | ^
+
+warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

恩,虽然将network::connect设为pub了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的network模块却不是公有的。这回我们是自内向外修改库文件的,而client::connect的时候是自外向内修改的。我们需要修改 src/lib.rsnetwork 也是公有的:

+

Filename: src/lib.rs

+
pub mod client;
+
+pub mod network;
+
+

现在再编译的话,那个警告就消失了:

+
warning: function is never used: `connect`, #[warn(dead_code)] on by default
+ --> src/network/server.rs:1:1
+  |
+1 | fn connect() {
+  | ^
+
+

只剩一个警告了!尝试自食其力修改它吧!

+

私有性规则

+

总的来说,有如下项的可见性规则:

+
    +
  1. 如果一个项是公有的,它能被任何父模块访问
  2. +
  3. 如果一个项是私有的,它只能被当前模块或其子模块访问
  4. +
+

私有性示例

+

让我们看看更多例子作为练习。创建一个新的库项目并在新项目的 src/lib.rs 输入列表 7-5 中的代码:

+
+Filename: src/lib.rs +
mod outermost {
+    pub fn middle_function() {}
+
+    fn middle_secret_function() {}
+
+    mod inside {
+        pub fn inner_function() {}
+
+        fn secret_function() {}
+    }
+}
+
+fn try_me() {
+    outermost::middle_function();
+    outermost::middle_secret_function();
+    outermost::inside::inner_function();
+    outermost::inside::secret_function();
+}
+
+
+

Listing 7-5: Examples of private and public functions, some of which are +incorrect

+
+
+

在尝试编译这些代码之前,猜测一下try_me函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论!

+

检查错误

+

try_me函数位于项目的根模块。叫做outermost的模块是私有的,不过第二条私有性规则说明try_me函数允许访问outermost模块,因为outermost位于当前(根)模块,try_me也是。

+

outermost::middle_function的调用是正确的。因为middle_function是公有的,而try_me通过其父模块访问middle_functionoutermost。根据上一段的规则我们可以确定这个模块是可访问的。

+

outermost::middle_secret_function的调用会造成一个编译错误。middle_secret_function是私有的,所以第二条(私有性)规则生效了。根模块既不是middle_secret_function的当前模块(outermost是),也不是middle_secret_function当前模块的子模块。

+

叫做inside的模块是私有的且没有子模块,所以它只能被当前模块访问,outermost。这意味着try_me函数不允许调用outermost::inside::inner_functionoutermost::inside::secret_function任何一个。

+

修改错误

+

这里有一些尝试修复错误的代码修改意见。在你尝试他们之前,猜测一下他们哪个能修复错误,接着编译查看你是否猜对了,并结合私有性规则理解为什么。

+
    +
  • 如果inside模块是公有的?
  • +
  • 如果outermost是公有的而inside是私有的?
  • +
  • 如果在inner_function函数体中调用::outermost::middle_secret_function()?(开头的两个冒号意味着从根模块开始引用模块。)
  • +
+

请随意设计更多的实验并尝试理解他们!

+

接下来,让我们讨论一下使用use关键字来将项引入作用域。

+

使用use导入命名空间

+
+

ch07-03-importing-names-with-use.md +
+commit e2a129961ae346f726f8b342455ec2255cdfed68

+
diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 27cb06b..885dafb 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -32,5 +32,5 @@ - [模块](ch07-00-modules.md) - [`mod`和文件系统](ch07-01-mod-and-the-filesystem.md) - - [使用`pub`空值可见性](ch07-02-controlling-visibility-with-pub.md) + - [使用`pub`控制可见性](ch07-02-controlling-visibility-with-pub.md) - [使用`use`导入命名空间](ch07-03-importing-names-with-use.md) \ No newline at end of file diff --git a/src/ch07-00-modules.md b/src/ch07-00-modules.md index e69de29..792bf2c 100644 --- a/src/ch07-00-modules.md +++ b/src/ch07-00-modules.md @@ -0,0 +1,15 @@ +# 模块 + +> [ch07-00-modules.md](https://github.com/rust-lang/book/blob/master/src/ch07-00-modules.md) +>
+> commit e2a129961ae346f726f8b342455ec2255cdfed68 + +在你刚开始编写 Rust 程序时,代码可能仅仅位于`main`函数里。随着代码数量的增长,最终你会将功能移动到其他函数中,为了复用也为了更好的组织。通过将代码分隔成更小的块,每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢?Rust 有一个模块系统来处理编写可复用代码同时保持代码组织度的问题。 + +就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。**模块**(*module*)是一个包含函数或类型定义的命名空间,你可以选择这些定义是能(公有)还是不能(私有)在其模块外可见。这是一个模块如何工作的概括: + +* 使用`mod`关键字声明模块 +* 默认所有内容都是私有的(包括模块自身)。可以使用`pub`关键字将其变成公有并在其命名空间外可见。 +* `use`关键字允许引入模块、或模块中的定义到作用域中以便于引用他们。 + +我们会逐一了解这每一部分并学习如何将他们结合在一起。 \ No newline at end of file diff --git a/src/ch07-01-mod-and-the-filesystem.md b/src/ch07-01-mod-and-the-filesystem.md index e69de29..7fe7253 100644 --- a/src/ch07-01-mod-and-the-filesystem.md +++ b/src/ch07-01-mod-and-the-filesystem.md @@ -0,0 +1,356 @@ +## `mod`和文件系统 + +> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/src/ch07-01-mod-and-the-filesystem.md) +>
+> commit e2a129961ae346f726f8b342455ec2255cdfed68 + +我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过我们不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章我们见过的`rand`就是这样的 crate。 + +我们将创建一个提供一些通用网络功能的项目的骨架结构;我们将专注于模块和函数的组织,而不担心函数体中的具体代码。这个项目叫做`communicator`。Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入`--bin`参数则项目将会是一个库: + +``` +$ cargo new communicator +$ cd communicator +``` + +注意 Cargo 生成了 *src/lib.rs* 而不是 *src/main.rs*。在 *src/lib.rs* 中我们会找到这些: + +Filename: src/lib.rs + +```rust +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} +``` + +Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用`--bin`参数那样创建一个“Hello, world!”二进制项目。稍后一点会介绍`#[]`和`mod tests`语法,目前只需确保他们位于 *src/lib.rs* 中。 + +因为没有 *src/main.rs* 文件,所以没有可供 Cargo 的`cargo run`执行的东西。因此,我们将使用`cargo build`命令只是编译库 crate 的代码。 + +我们将学习根据编写代码的意图来选择不同的织库项目代码组织来适应多种场景。 + +### 模块定义 + +对于`communicator`网络库,首先我们要定义一个叫做`network`的模块,它包含一个叫做`connect`的函数定义。Rust 中所有模块的定义以关键字`mod`开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码: + +Filename: src/lib.rs + +```rust +mod network { + fn connect() { + } +} +``` + +`mod`关键字的后面是模块的名字,`network`,接着是位于大括号中的代码块。代码块中的一切都位于`network`命名空间中。在这个例子中,只有一个函数,`connect`。如果想要在`network`模块外面的代码中调用这个函数,需要指定模块名并使用命名空间语法`::`,像这样:`network::connect()`,而不是只是`connect()`。 + +也可以在 *src/lib.rs* 文件中同时存在多个模块。例如,再拥有一个`client`模块,它也有一个叫做`connect`的函数,如列表 7-1 中所示那样增加这个模块: + + +
+Filename: src/lib.rs + +```rust +mod network { + fn connect() { + } +} + +mod client { + fn connect() { + } +} +``` + +
+ +Listing 7-1: The `network` module and the `client` module defined side-by-side +in *src/lib.rs* + +
+
+ +现在我们有了`network::connect`函数和`client::connect`函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突因为他们位于不同的模块。 + +虽然在这个例子中,我们构建了一个库,但是 *src/lib.rs* 并没有什么特殊意义。也可以在 *src/main.rs* 中使用子模块。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,`client`模块和它的函数`connect`可能放在`network`命名空间里显得更有道理,如列表 7-2 所示: + +
+Filename: src/lib.rs + +```rust +mod network { + fn connect() { + } + + mod client { + fn connect() { + } + } +} +``` + +
+ +Listing 7-2: Moving the `client` module inside of the `network` module + +
+
+ +在 *src/lib.rs* 文件中,将现有的`mod network`和`mod client`的定义替换为`client`模块作为`network`的一个内部模块。现在我们有了`network::connect`和`network::client::connect`函数:又一次,这两个`connect`函数也不相冲突,因为他们在不同的命名空间中。 + +这样,模块之间形成了一个层次结构。*src/lib.rs* 的内容位于最顶层,而其子模块位于较低的层次。这是列表 7-1 中的例子以这种方式考虑的组织结构: + +``` +communicator + ├── network + └── client +``` + +而这是列表 7-2 中例子的的结构: + +``` +communicator + └── network + └── client +``` + +可以看到列表 7-2 中,`client`是`network`的子模块,而不是它的同级模块。更为负责的项目可以有很多的模块,所以他们需要符合逻辑地组合在一起以便记录他们。在项目中“符合逻辑”的意义全凭你得理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块将是你会喜欢的结构。 + +### 将模块移动到其他文件 + +位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不是所有的内容都落到 *src/lib.rs* 中了。作为例子,我们将从列表 7-3 中的代码开始: + +
+Filename: src/lib.rs + +```rust +mod client { + fn connect() { + } +} + +mod network { + fn connect() { + } + + mod server { + fn connect() { + } + } +} +``` + +
+ +Listing 7-3: Three modules, `client`, `network`, and `network::server`, all +defined in *src/lib.rs* + +
+
+ +这是模块层次结构: + +``` +communicator + ├── client + └── network + └── server +``` + +这里我们仍然**定义**了`client`模块,不过去掉了大括号和`client`模块中的定义并替换为一个分号,这使得 Rust 知道去其他地方寻找模块中定义的代码。 + +那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步`client`模块中被去掉的`connect`函数: + +Filename: src/client.rs + +```rust +fn connect() { +} +``` + +注意这个文件中并不需要一个`mod`声明;因为已经在 *src/lib.rs* 中已经使用`mod`声明了`client`模块。这个文件仅仅提供`client`模块的内容。如果在这里加上一个`mod client`,那么就等于给`client`模块增加了一个叫做`client`的子模块! + +Rust 默认只知道 *src/lib.rs* 中的内容。如果想要对项目加入更多文件,我们需要在 *src/lib.rs* 中告诉 Rust 去寻找其他文件;这就是为什么`mod client`需要被定义在 *src/lib.rs* 而不是在 *src/client.rs*。 + +现在,一切应该能成功编译,虽然会有一些警告。记住使用`cargo build`而不是`cargo run`因为这是一个库 crate 而不是二进制 crate: + +``` +$ cargo build + Compiling communicator v0.1.0 (file:///projects/communicator) + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/client.rs:1:1 + | +1 | fn connect() { + | ^ + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/lib.rs:4:5 + | +4 | fn connect() { + | ^ + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/lib.rs:8:9 + | +8 | fn connect() { + | ^ +``` + +这些警告提醒我们有从未被使用的函数。目前不用担心这些警告;在本章的后面会解决他们。好消息是,他们仅仅是警告;我们的项目能够被成功编译。 + +下面使用相同的模式将`network`模块提取到它自己的文件中。删除 *src/lib.rs* 中`network`模块的内容并在声明后加上一个分号,像这样: + +Filename: src/lib.rs + +```rust,ignore +mod client; + +mod network; +``` + +接着新建 *src/network.rs* 文件并输入如下内容: + +Filename: src/network.rs + +```rust +fn connect() { +} + +mod server { + fn connect() { + } +} +``` + +注意这个模块文件中我们也使用了一个`mod`声明;这是因为我们希望`server`成为`network`的一个子模块。 + +现在再次运行`cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用`mod server;`替换`server`模块的内容: + +Filename: src/network.rs + +```rust,ignore +fn connect() { +} + +mod server; +``` + +接着创建 *src/server.rs* 文件并输入需要提取的`server`模块的内容: + +Filename: src/server.rs + +```rust +fn connect() { +} +``` + +当尝试运行`cargo build`时,会出现如列表 7-4 中所示的错误: + +
+ +```text +$ cargo build + Compiling communicator v0.1.0 (file:///projects/communicator) +error: cannot declare a new module at this location + --> src/network.rs:4:5 + | +4 | mod server; + | ^^^^^^ + | +note: maybe move this module `network` to its own directory via `network/mod.rs` + --> src/network.rs:4:5 + | +4 | mod server; + | ^^^^^^ +note: ... or maybe `use` the module `server` instead of possibly redeclaring it + --> src/network.rs:4:5 + | +4 | mod server; + | ^^^^^^ +``` + +
+ +Listing 7-4: Error when trying to extract the `server` submodule into +*src/server.rs* + +
+
+ +这个错误说明“不能在这个位置新声明一个模块”并指出 *src/network.rs* 中的`mod server;`这一行。看来 *src/network.rs* 与 *src/lib.rs* 在某些方面是不同的;让我们继续阅读以理解这是为什么。 + +列表 7-4 中间的记录事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作: + +```text +note: maybe move this module `network` to its own directory via `network/mod.rs` +``` + +我们可以按照记录所建议的去操作,而不是继续使用之前的与模块同名的文件的模式: + +1. 新建一个叫做 *network* 的**目录**,这是父模块的名字 +2. 将 *src/network.rs* 移动到新建的 *network* 目录中并重命名,现在它是 *src/network/mod.rs* +3. 将子模块文件 *src/server.rs* 移动到 *network* 目录中 + +如下是执行这些步骤的命令: + +```sh +$ mkdir src/network +$ mv src/network.rs src/network/mod.rs +$ mv src/server.rs src/network +``` + +现在如果运行`cargo build`的话将顺利编译(虽然仍有警告)。现在模块的布局看起来仍然与列表 7-3 中所有代码都在 *src/lib.rs* 中时完全一样: + +``` +communicator + ├── client + └── network + └── server +``` + +对应的文件布局现在看起来像这样: + +``` +├── src +│ ├── client.rs +│ ├── lib.rs +│ └── network +│ ├── mod.rs +│ └── server.rs +``` + +那么,当我们想要提取`network::server`模块时,为什么也必须将 *src/network.rs* 文件改名成 *src/network/mod.rs* 文件呢,还有为什么要将`network::server`的代码放入 *network* 目录的 *src/network/server.rs* 文件中,而不能将`network::server`模块提取到 *src/server.rs* 中呢?原因是如果 *server.rs* 文件在 *src* 目录中那么 Rust 就不能知道`server`应当是`network`的子模块。为了更清除的说明为什么 Rust 不知道,让我们考虑一下有着如下层级的另一个例子,它的所有定义都位于 *src/lib.rs* 中: + +``` +communicator + ├── client + └── network + └── client +``` + +在这个例子中,仍然有这三个模块,`client`、`network`和`network::client`。如果按照与上面最开始将模块提取到文件中相同的步骤来操作,对于`client`模块会创建 *src/client.rs*。对于`network`模块,会创建 *src/network.rs*。但是接下来不能将`network::client`模块提取到 *src/client.rs* 文件中,因为它已经存在了,对应顶层的`client`模块!如果将`client`和`network::client`的代码都放入 *src/client.rs* 文件,Rust 将无从可知这些代码是属于`client`还是`network::client`的。 + +因此,一旦想要将`network`模块的子模块`network::client`提取到一个文件中,需要为`network`模块新建一个目录替代 *src/network.rs* 文件。接着`network`模块的代码将进入 *src/network/mod.rs* 文件,而子模块`network::client`将拥有其自己的文件 *src/network/client.rs*。现在顶层的 *src/client.rs* 中的代码毫无疑问的都属于`client`模块。 + +### 模块文件系统的规则 + +与文件系统相关的模块规则总结如下: + +* 如果一个叫做`foo`的模块没有子模块,应该将`foo`的声明放入叫做 *foo.rs* 的文件中。 +* 如果一个叫做`foo`的模块有子模块,应该将`foo`的声明放入叫做 *foo/mod.rs* 的文件中。 + +这些规则适用于递归(嵌套),所以如果`foo`模块有一个子模块`bar`而`bar`没有子模块,则 *src* 目录中应该有如下文件: + +``` +├── foo +│ ├── bar.rs (contains the declarations in `foo::bar`) +│ └── mod.rs (contains the declarations in `foo`, including `mod bar`) +``` + +模块自身则应该使用`mod`关键字定义于父模块的文件中。 + +接下来,我们讨论一下`pub`关键字,并除掉那些警告! \ No newline at end of file diff --git a/src/ch07-02-controlling-visibility-with-pub.md b/src/ch07-02-controlling-visibility-with-pub.md index e69de29..e86f8e7 100644 --- a/src/ch07-02-controlling-visibility-with-pub.md +++ b/src/ch07-02-controlling-visibility-with-pub.md @@ -0,0 +1,230 @@ +## 使用`pub`控制可见性 + +> [ch07-02-controlling-visibility-with-pub.md](https://github.com/rust-lang/book/blob/master/src/ch07-02-controlling-visibility-with-pub.md) +>
+> commit e2a129961ae346f726f8b342455ec2255cdfed68 + +我们通过将`network`和`network::server`的代码分别移动到 *src/network/mod.rs* 和 *src/network/server.rs* 文件中解决了列表 7-4 中出现的错误信息。现在,`cargo build`能够构建我们的项目,不过仍然有一些警告信息,表示`client::connect`、`network::connect`和`network::server::connect`函数没有被使用: + +``` +warning: function is never used: `connect`, #[warn(dead_code)] on by default +src/client.rs:1:1 + | +1 | fn connect() { + | ^ + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/mod.rs:1:1 + | +1 | fn connect() { + | ^ + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/server.rs:1:1 + | +1 | fn connect() { + | ^ +``` + +那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被**用户**使用,而不一定要被项目自身使用,所以不应该担心这些函数是未被使用的。创建他们的意义就在于被另一个项目而不是被自己使用。 + +为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个`connect`库,从外部调用他们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate: + +Filename: src/main.rs + +```rust,ignore +extern crate communicator; + +fn main() { + communicator::client::connect(); +} +``` + +使用`extern crate`指令将`communicator`库 crate 引入到作用域,因为事实上我们的包包含**两个** crate。Cargo 认为 *src/main.rs* 是一个二进制 crate 的根文件,与现存的以 *src/lib.rs* 为根文件的库 crate 相区分。这个模式在可执行项目中非常常见:大部分功能位于库 crate 中,而二进制 crate 使用这个库 crate。通过这种方式,其他程序也可以使用这个库 crate,这是一个很好的关注分离(separation of concerns)。 + +从一个外部 crate 的视角观察`communicator`库的内部,我们创建的所有模块都位于一个与 crate 同名的模块内部,`communicator`。这个顶层的模块被称为 crate 的**根模块**(*root module*)。 + +另外注意到即便在项目的子模块中使用外部 crate,`extern crate`也应该位于根模块(也就是 *src/main.rs* 或 *src/lib.rs*)。接着,在子模块中,我们就可以像顶层模块那样引用外部 crate 中的项了。 + +我们的二进制 crate 如今正好调用了库中`client`模块的`connect`函数。然而,执行`cargo build`会在之前的警告之后出现一个错误: + +``` +error: module `client` is private + --> src/main.rs:4:5 + | +4 | communicator::client::connect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``` + +啊哈!这告诉了我们`client`模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到**公有**和**私有**的概念。Rust 所有代码的默认状态是私有的:除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数,因为程序自身是唯一允许使用这个函数的代码,Rust 会警告说函数未被使用。 + +一旦我们指定一个像`client::connect`的函数为公有,不光二进制 crate 中的函数调用会被允许,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了我们意在使函数在我们程序的外部被使用。现在这个可能的理论上的外部可用性使 Rust 认为这个函数“已经被使用”。因此。当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告某项未被使用。 + +### 标记函数为公有 + +为了告诉 Rust 某项为公有,在想要标记为公有的项的声明开头加上`pub`关键字。现在我们将致力于修复`client::connect`未被使用的警告,以及二进制 crate 中“模块`client`是私有的”的错误。像这样修改 *src/lib.rs* 使`client`模块公有: + +Filename: src/lib.rs + +```rust,ignore +pub mod client; + +mod network; +``` + +`pub`写在`mod`之前。再次尝试构建: + +``` + +error: function `connect` is private + --> src/main.rs:4:5 + | +4 | communicator::client::connect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``` + +非常好!另一个不同的错误!好的,不同的错误信息是值得庆祝的(可能是程序员被黑的最惨的一次)。新错误表明“函数`connect`是私有的”,那么让我们修改 *src/client.rs* 将`client::connect`也设为公有: + +Filename: src/client.rs + +```rust +pub fn connect() { +} +``` + +再再一次运行`cargo build`: + +``` +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/mod.rs:1:1 + | +1 | fn connect() { + | ^ + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/server.rs:1:1 + | +1 | fn connect() { + | ^ +``` + +编译通过了,关于`client::connect`未被使用的警告消失了! + +未被使用的代码并不总是意味着他们需要被设为公有的:如果你**不**希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除他们。这也可能是警告你出 bug,如果你刚刚不小心删除了库中所有这个函数的调用。 + +当然我们的情况是,**确实**希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为`pub`并去掉剩余的警告。修改 *src/network/mod.rs* 为: + +Filename: src/network/mod.rs + +```rust,ignore +pub fn connect() { +} + +mod server; +``` + +并编译: + +``` +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/mod.rs:1:1 + | +1 | pub fn connect() { + | ^ + +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/server.rs:1:1 + | +1 | fn connect() { + | ^ +``` + +恩,虽然将`network::connect`设为`pub`了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的`network`模块却不是公有的。这回我们是自内向外修改库文件的,而`client::connect`的时候是自外向内修改的。我们需要修改 *src/lib.rs* 让 `network` 也是公有的: + + +Filename: src/lib.rs + +```rust,ignore +pub mod client; + +pub mod network; +``` + +现在再编译的话,那个警告就消失了: + +``` +warning: function is never used: `connect`, #[warn(dead_code)] on by default + --> src/network/server.rs:1:1 + | +1 | fn connect() { + | ^ +``` + +只剩一个警告了!尝试自食其力修改它吧! + +### 私有性规则 + +总的来说,有如下项的可见性规则: + +1. 如果一个项是公有的,它能被任何父模块访问 +2. 如果一个项是私有的,它只能被当前模块或其子模块访问 + +### 私有性示例 + +让我们看看更多例子作为练习。创建一个新的库项目并在新项目的 *src/lib.rs* 输入列表 7-5 中的代码: + +
+Filename: src/lib.rs + +```rust,ignore +mod outermost { + pub fn middle_function() {} + + fn middle_secret_function() {} + + mod inside { + pub fn inner_function() {} + + fn secret_function() {} + } +} + +fn try_me() { + outermost::middle_function(); + outermost::middle_secret_function(); + outermost::inside::inner_function(); + outermost::inside::secret_function(); +} +``` + +
+ +Listing 7-5: Examples of private and public functions, some of which are +incorrect + +
+
+ +在尝试编译这些代码之前,猜测一下`try_me`函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论! + +#### 检查错误 + +`try_me`函数位于项目的根模块。叫做`outermost`的模块是私有的,不过第二条私有性规则说明`try_me`函数允许访问`outermost`模块,因为`outermost`位于当前(根)模块,`try_me`也是。 + +`outermost::middle_function`的调用是正确的。因为`middle_function`是公有的,而`try_me`通过其父模块访问`middle_function`,`outermost`。根据上一段的规则我们可以确定这个模块是可访问的。 + +`outermost::middle_secret_function`的调用会造成一个编译错误。`middle_secret_function`是私有的,所以第二条(私有性)规则生效了。根模块既不是`middle_secret_function`的当前模块(`outermost`是),也不是`middle_secret_function`当前模块的子模块。 + +叫做`inside`的模块是私有的且没有子模块,所以它只能被当前模块访问,`outermost`。这意味着`try_me`函数不允许调用`outermost::inside::inner_function`或`outermost::inside::secret_function`任何一个。 + +#### 修改错误 + +这里有一些尝试修复错误的代码修改意见。在你尝试他们之前,猜测一下他们哪个能修复错误,接着编译查看你是否猜对了,并结合私有性规则理解为什么。 + +* 如果`inside`模块是公有的? +* 如果`outermost`是公有的而`inside`是私有的? +* 如果在`inner_function`函数体中调用`::outermost::middle_secret_function()`?(开头的两个冒号意味着从根模块开始引用模块。) + +请随意设计更多的实验并尝试理解他们! + +接下来,让我们讨论一下使用`use`关键字来将项引入作用域。 \ No newline at end of file diff --git a/src/ch07-03-importing-names-with-use.md b/src/ch07-03-importing-names-with-use.md index e69de29..d28233c 100644 --- a/src/ch07-03-importing-names-with-use.md +++ b/src/ch07-03-importing-names-with-use.md @@ -0,0 +1,6 @@ +## 使用`use`导入命名空间 + +> [ch07-03-importing-names-with-use.md](https://github.com/rust-lang/book/blob/master/src/ch07-03-importing-names-with-use.md) +>
+> commit e2a129961ae346f726f8b342455ec2255cdfed68 +