mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-01-24 16:10:25 +08:00
873 lines
23 KiB
Markdown
873 lines
23 KiB
Markdown
|
|
|||
|
[TOC]
|
|||
|
|
|||
|
# Modules
|
|||
|
|
|||
|
When you write a program in Rust, your code might start off living solely in
|
|||
|
the `main` function. As your code grows, you eventually move functionality out
|
|||
|
into functions, both for re-use and for nicer organization. By splitting your
|
|||
|
code up into smaller chunks, each chunk is easier to understand on its own. So
|
|||
|
what happens when you start having too many functions? Rust has a module system
|
|||
|
that tackles both the problem of wanting to be able to re-use code and the
|
|||
|
problem of keeping your code organized.
|
|||
|
|
|||
|
In the same way that you extract lines of code into a function, you can extract
|
|||
|
functions (and other code like structs and enums too) into different modules. A
|
|||
|
*module* is a namespace that contains definitions of functions or types, and
|
|||
|
those definitions can be visible outside their module or not. Here's an
|
|||
|
overview of how the bits fit together:
|
|||
|
|
|||
|
* `mod` declares a new module.
|
|||
|
* Everything starts off as private, but the `pub` keyword makes it public.
|
|||
|
* The `use` keyword allows you to bring modules, or definitions inside of them,
|
|||
|
into scope so that it's easier to refer to them.
|
|||
|
|
|||
|
We'll take a look at each of these parts and see how they fit into the whole.
|
|||
|
|
|||
|
## `mod` and the Filesystem
|
|||
|
|
|||
|
Every module in Rust starts with the `mod` keyword. In this next example, we'll
|
|||
|
start again by making a new project with Cargo. This time, instead of a binary,
|
|||
|
we're going to make a library: a project that other people would pull into their
|
|||
|
projects as a dependency. We saw this with the `rand` crate in Chapter 2.
|
|||
|
|
|||
|
Imagine that we're creating a library to provide some general networking
|
|||
|
functionality, and we decide to call our library `communicator`. To create this
|
|||
|
library, we won't use the `--bin` option like we have before. This is because
|
|||
|
by default cargo will create a library:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo new communicator
|
|||
|
$ cd communicator
|
|||
|
```
|
|||
|
|
|||
|
Notice that Cargo generated `src/lib.rs` instead of `src/main.rs` for us, and
|
|||
|
inside it we'll find this:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
#[cfg(test)]
|
|||
|
mod tests {
|
|||
|
#[test]
|
|||
|
fn it_works() {
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This is an empty test to help us get our library started, instead of the binary
|
|||
|
that says "Hello, world!" that we get with the `--bin` option. Let's ignore the
|
|||
|
`#[]` stuff and `mod tests` for a little bit, but we'll make sure to leave it
|
|||
|
in `src/lib.rs` for later.
|
|||
|
|
|||
|
We're going to look at different ways we could choose to organize our library's
|
|||
|
code, any of which could make sense depending on exactly what we were trying to
|
|||
|
do. To start, add this code at the beginning of the file:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
mod network {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This is our first module declaration. We use the `mod` keyword, followed by the
|
|||
|
name of the module, and then a block of code in curly braces. Everything inside
|
|||
|
this block is inside the namespace `network`. In this case, we have a single
|
|||
|
function, `connect`. If we wanted to try and call this function from outside
|
|||
|
the `network` module, we would say `network::connect()` rather than `connect()`.
|
|||
|
|
|||
|
We could have multiple modules, side-by-side. For example, if we wanted a
|
|||
|
`client` module:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
mod network {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
mod client {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Now we have a `network::connect` function and a `client::connect` function.
|
|||
|
|
|||
|
And we can put modules inside of modules. If we wanted to have `client` be
|
|||
|
within `network`:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
mod network {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
|
|||
|
mod client {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This gives us `network::connect` and `network::client::connect`.
|
|||
|
|
|||
|
In this way, modules form a tree. The contents of `src/lib.rs` are at the root
|
|||
|
of the project's tree, and the submodules form the leaves. Here's what our
|
|||
|
first example looks like when thought of this way:
|
|||
|
|
|||
|
```text
|
|||
|
communicator
|
|||
|
├── network
|
|||
|
└── client
|
|||
|
```
|
|||
|
|
|||
|
And here's the second:
|
|||
|
|
|||
|
```text
|
|||
|
communicator
|
|||
|
└── network
|
|||
|
└── client
|
|||
|
```
|
|||
|
|
|||
|
More complicated projects can have a lot of modules.
|
|||
|
|
|||
|
### Putting Modules in Another File
|
|||
|
|
|||
|
Modules form a hierarchical, tree-like structure. So does another thing:
|
|||
|
file systems! The module system is the way that we split larger Rust projects up
|
|||
|
into multiple files. Let's imagine we have a module layout like this:
|
|||
|
|
|||
|
File: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
mod client {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
mod network {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
|
|||
|
mod server {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Let's extract the `client` module into another file. First, we need to change
|
|||
|
our code in `src/lib.rs`:
|
|||
|
|
|||
|
File: src/lib.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
mod client;
|
|||
|
|
|||
|
mod network {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
|
|||
|
mod server {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We still say `mod client`, but instead of curly braces, we have a semicolon.
|
|||
|
This lets Rust know that we have a module, but it's in another file with that
|
|||
|
module's name. Open up `src/client.rs` and put this in it:
|
|||
|
|
|||
|
File: src/client.rs
|
|||
|
|
|||
|
```rust
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Note that we don't need a `mod` declaration in this file. `mod` is for
|
|||
|
declaring a new module, and we've already declared this module in `src/lib.rs`.
|
|||
|
This file provides the _contents_ of the `client` module. If we put a `mod
|
|||
|
client` here, we'd be giving the `client` module its own submodule named
|
|||
|
`client`!
|
|||
|
|
|||
|
Now, everything should compile successfully, but with a few warnings:
|
|||
|
|
|||
|
```bash
|
|||
|
$ 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() {
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
Don't worry about those warnings for now; we'll clear them up in a future
|
|||
|
section. They're just warnings, we've built things successfully!
|
|||
|
|
|||
|
Let's extract the `network` module into its own file next, using the same
|
|||
|
pattern. Change `src/lib.rs` to look like this:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
mod client;
|
|||
|
|
|||
|
mod network;
|
|||
|
```
|
|||
|
|
|||
|
And then put this in `src/network.rs`
|
|||
|
|
|||
|
Filename: src/network.rs
|
|||
|
|
|||
|
```rust
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
|
|||
|
mod server {
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
And then run `cargo build` again. Success! We have one more module to extract:
|
|||
|
`server`. Unfortunately, our current tactic of extracting a module into a file
|
|||
|
named after that module won't work. Let's try it anyway. Modify
|
|||
|
`src/network.rs` to look like this:
|
|||
|
|
|||
|
Filename: src/network.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
|
|||
|
mod server;
|
|||
|
```
|
|||
|
|
|||
|
Put this in `src/server.rs`
|
|||
|
|
|||
|
Filename: src/server.rs
|
|||
|
|
|||
|
```rust
|
|||
|
fn connect() {
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
When we try to `cargo build`, we'll get an error:
|
|||
|
|
|||
|
```bash
|
|||
|
$ 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;
|
|||
|
| ^^^^^^
|
|||
|
```
|
|||
|
|
|||
|
This error is actually pretty helpful. It points out something we didn't know
|
|||
|
that we could do yet:
|
|||
|
|
|||
|
> note: maybe move this module `network` to its own directory via
|
|||
|
`network/mod.rs`
|
|||
|
|
|||
|
Here's the problem: in our case, we have different names for our modules:
|
|||
|
`client` and `network::server`. But what if we had `client` and
|
|||
|
`network::client`, or `server` and `network::server`? Having two modules at
|
|||
|
different places in the module hierarchy have the same name is completely
|
|||
|
valid, but then which module would the files `src/client.rs` and
|
|||
|
`src/server.rs`, respectively, be for?
|
|||
|
|
|||
|
Instead of continuing to follow the same file naming pattern we used
|
|||
|
previously, we can do what the error suggests. We'll make a new _directory_,
|
|||
|
move `src/server.rs` into it, and change `src/network.rs` to
|
|||
|
`src/network/mod.rs`. Then, when we try to build:
|
|||
|
|
|||
|
```bash
|
|||
|
$ mkdir src/network
|
|||
|
$ mv src/server.rs src/network
|
|||
|
$ mv src/network.rs src/network/mod.rs
|
|||
|
$ cargo build
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
<warnings>
|
|||
|
$
|
|||
|
```
|
|||
|
|
|||
|
It works! So now our module layout looks like this:
|
|||
|
|
|||
|
```text
|
|||
|
communicator
|
|||
|
├── client
|
|||
|
└── network
|
|||
|
└── server
|
|||
|
```
|
|||
|
|
|||
|
And the corresponding file layout looks like this:
|
|||
|
|
|||
|
```text
|
|||
|
├── src
|
|||
|
│ ├── client.rs
|
|||
|
│ ├── lib.rs
|
|||
|
│ └── network
|
|||
|
│ ├── mod.rs
|
|||
|
│ └── server.rs
|
|||
|
```
|
|||
|
|
|||
|
In summary, these are the rules of modules with regards to files:
|
|||
|
|
|||
|
* If a module named `foo` has no submodules, you should put the declarations in
|
|||
|
the `foo` module in a file named `foo.rs`.
|
|||
|
* If a module named `foo` does have submodules, you should put the declarations
|
|||
|
for `foo` in a file named `foo/mod.rs`.
|
|||
|
* The first two rules apply recursively, so that if a module named `foo` has a
|
|||
|
submodule named `bar` and `bar` does not have submodules, you should have the
|
|||
|
following files in your `src` directory:
|
|||
|
|
|||
|
```text
|
|||
|
├── foo
|
|||
|
│ ├── bar.rs (contains the declarations in `foo::bar`)
|
|||
|
│ └── mod.rs (contains the declarations in `foo`, including `mod bar`)
|
|||
|
```
|
|||
|
|
|||
|
* The modules themselves should be declared in their parent module's file using
|
|||
|
the `mod` keyword.
|
|||
|
|
|||
|
Next, we'll talk about the `pub` keyword, and get rid of those warnings!
|
|||
|
|
|||
|
## Controlling Visibility with `pub`
|
|||
|
|
|||
|
At the end of the last section, we had a project, `communicator`, and when we compiled it, we got some strange warnings:
|
|||
|
|
|||
|
```bash
|
|||
|
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/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() {
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
Why does this happen? After all, we're building a library. What if these three
|
|||
|
functions are the public interface that we want our *users* to use? We won't
|
|||
|
necessarily be using them within our own project, but the point of creating them
|
|||
|
is that they *will* be used by another project. Let's try using them as if we
|
|||
|
were another project using our library to see what happens and understand why
|
|||
|
we're getting these unused function warnings. Create a `src/main.rs` file with
|
|||
|
this code:
|
|||
|
|
|||
|
Filename: src/main.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
extern crate communicator;
|
|||
|
|
|||
|
fn main() {
|
|||
|
communicator::client::connect();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We need the `extern crate` line to bring our `communicator` library crate into
|
|||
|
scope, because our package actually now contains *two* crates. Cargo treats
|
|||
|
src/main.rs as the crate root of a binary crate, and we also have our existing
|
|||
|
library crate. This pattern is quite common for executable crates: most
|
|||
|
functionality is in a library crate, and the executable crate uses that
|
|||
|
library. This way, other programs can also use the library crate, and it’s a
|
|||
|
nice separation of concerns.
|
|||
|
|
|||
|
Our binary crate right now just calls our library's `connect` function from
|
|||
|
the `client` module; we picked that one since it's the first warning in our
|
|||
|
build output above. Invoking `cargo build` will now give us an error after the
|
|||
|
warnings:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo build
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
error: module `client` is private
|
|||
|
--> src/main.rs:4:5
|
|||
|
|
|
|||
|
4 | communicator::client::connect();
|
|||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|||
|
```
|
|||
|
|
|||
|
Ah ha! The `client` module is private. This is the first time we've run into
|
|||
|
the concepts of 'public' and 'private' in the context of Rust. There's no
|
|||
|
keyword to make something private; that's the default state. In this default
|
|||
|
state, no one else could possibly use it, so if we don't use it within our
|
|||
|
library crate, Rust will warn us that it's unused. Once we tell Rust something
|
|||
|
is public, Rust knows that we intend for code external to our crate to use it,
|
|||
|
and Rust considers theoretical external usage that is now possible to count as
|
|||
|
being used. Thus, when something is marked as public, Rust will stop warning us
|
|||
|
that it is unused.
|
|||
|
|
|||
|
To tell Rust we want to make something public, we add the `pub` keyword. This
|
|||
|
keyword goes before the declaration of the item we want to make public. Let's
|
|||
|
modify `src/lib.rs` to make the `client` module public to fix the error we got:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
pub mod client;
|
|||
|
|
|||
|
mod network;
|
|||
|
```
|
|||
|
|
|||
|
The `pub` goes right before `mod`. Let's try building again:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo build
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
<warnings>
|
|||
|
error: function `connect` is private
|
|||
|
--> src/main.rs:4:5
|
|||
|
|
|
|||
|
4 | communicator::client::connect();
|
|||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|||
|
```
|
|||
|
|
|||
|
Hooray! We have a different error! Yes, different error messages are a cause
|
|||
|
for celebration. The new error says "function `connect` is private", so let's
|
|||
|
edit `src/client.rs` to make `client::connect` public:
|
|||
|
|
|||
|
Filename: src/client.rs
|
|||
|
|
|||
|
```rust
|
|||
|
pub fn connect() {
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
And run `cargo build` again:
|
|||
|
|
|||
|
```bash
|
|||
|
cargo build
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
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() {
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
It compiled! And the warning about `client::connect` not being used is gone!
|
|||
|
|
|||
|
Making functions public isn't the only way to fix unused code warnings: if
|
|||
|
we *didn't* want these functions to be part of our public API and we got these
|
|||
|
warnings, the warnings could be alerting us to code we no longer needed and
|
|||
|
could safely delete. They could also be alerting us to a bug, if we
|
|||
|
had just accidentally removed all places within our library where we called
|
|||
|
this function.
|
|||
|
|
|||
|
However, we *do* want the other two functions to be part of our crate's public
|
|||
|
API, so let's mark them as `pub` as well to get rid of the remaining warnings.
|
|||
|
Modify `src/network/mod.rs` to be:
|
|||
|
|
|||
|
Filename: src/network/mod.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
pub fn connect() {
|
|||
|
}
|
|||
|
|
|||
|
mod server;
|
|||
|
```
|
|||
|
|
|||
|
And compile:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo build
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
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() {
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
Hmmm, it says this is still dead, even though it's `pub`. While the function is
|
|||
|
public within the module, the `network` module it's in is not public. We're
|
|||
|
working from the interior of the library out this time, as opposed to with
|
|||
|
`client` where we worked from the outside in. Let's change `src/lib.rs` to add
|
|||
|
the same fix though, by making `network` public like `client` is:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
pub mod client;
|
|||
|
|
|||
|
pub mod network;
|
|||
|
```
|
|||
|
|
|||
|
Now if we compile, that warning is gone:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo build
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
|||
|
--> src/network/server.rs:1:1
|
|||
|
|
|
|||
|
1 | fn connect() {
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
Only one last warning! Try to fix this one on your own!
|
|||
|
|
|||
|
### Privacy Rules
|
|||
|
|
|||
|
Overall, these are the rules for item visibility:
|
|||
|
|
|||
|
1. If an item is public, then it can be accessed through any of its
|
|||
|
parent modules.
|
|||
|
2. If an item is private, it may be accessed by the current module and its
|
|||
|
child modules.
|
|||
|
|
|||
|
Let's look at a few more examples to get some practice. What if we had this
|
|||
|
code in a new project's `src/lib.rs`:
|
|||
|
|
|||
|
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();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Before you try to compile this code, make a guess about which lines in
|
|||
|
`try_me` will have errors.
|
|||
|
|
|||
|
Ready? Let's talk through them!
|
|||
|
|
|||
|
The `try_me` function is in the root module of our project. The module named
|
|||
|
`outermost` is private, but the second rule says we're allowed to access it
|
|||
|
since `outermost` is in our current, root module.
|
|||
|
|
|||
|
The function call `outermost::middle_function()` will work. `middle_function`
|
|||
|
is public, and we are accessing it through its parent module, `outermost`,
|
|||
|
which we just determined we can access in the previous paragraph.
|
|||
|
|
|||
|
`outermost::middle_secret_function()` will cause a compilation error.
|
|||
|
`middle_secret_function` is private, so the second rule applies. Our current
|
|||
|
root module is neither the current module of `middle_secret_function`
|
|||
|
(`outermost` is), nor is it a child module of the current module of
|
|||
|
`middle_secret_function`.
|
|||
|
|
|||
|
The module named `inside` is private and has no child modules, so it can only
|
|||
|
be accessed by its current module, `outermost`. That means the `try_me`
|
|||
|
function is not allowed to call `outermost::inside::inner_function()` or
|
|||
|
`outermost::inside::secret_function()`.
|
|||
|
|
|||
|
Here are some changes to try making with this code. Try each one, make a guess
|
|||
|
about what will be allowed or not, compile to see if you're right, and use the
|
|||
|
rules to understand why.
|
|||
|
|
|||
|
* What if the `inside` module was public?
|
|||
|
* What if `outside` was public and `inside` was private?
|
|||
|
* What if, in the body of `inner_function`, we called
|
|||
|
`::outermost::middle_secret_function()`? (The two colons at the beginning
|
|||
|
mean that we want to refer to the namespaces starting from the root
|
|||
|
namespace.)
|
|||
|
|
|||
|
Feel free to design more experiments and try them out!
|
|||
|
|
|||
|
Next, let's talk about bringing items into a scope with the `use` keyword.
|
|||
|
|
|||
|
## Importing Names with `use`
|
|||
|
|
|||
|
We've seen how we can call functions defined within a module by using the
|
|||
|
module name as part of the call, like this:
|
|||
|
|
|||
|
Filename: src/main.rs
|
|||
|
|
|||
|
```rust
|
|||
|
pub mod a {
|
|||
|
pub mod series {
|
|||
|
pub mod of {
|
|||
|
pub fn namespaces() {}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fn main() {
|
|||
|
a::series::of::namespaces();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
However, referring to the fully qualified name can get quite lengthy, as we see
|
|||
|
in that example. To solve this issue, Rust has a keyword, `use`. It works like
|
|||
|
this:
|
|||
|
|
|||
|
Filename: src/main.rs
|
|||
|
|
|||
|
```rust
|
|||
|
pub mod a {
|
|||
|
pub mod series {
|
|||
|
pub mod of {
|
|||
|
pub fn namespaces() {}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
use a::series::of;
|
|||
|
|
|||
|
fn main() {
|
|||
|
of::namespaces();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We can `use` a module, and that will bring its name into scope. This allows us
|
|||
|
to shorten our function call, only requiring us to type the final module name,
|
|||
|
not the entire chain of them. `use` is quite powerful and can bring all kinds
|
|||
|
of things into scope. For example, we could `use` the function itself:
|
|||
|
|
|||
|
```rust
|
|||
|
pub mod a {
|
|||
|
pub mod series {
|
|||
|
pub mod of {
|
|||
|
pub fn namespaces() {}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
use a::series::of::namespaces;
|
|||
|
|
|||
|
fn main() {
|
|||
|
namespaces();
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Enums also form this kind of namespace; we can import an enum's variants with
|
|||
|
`use` as well. For any kind of `use` statement, if you are importing multiple
|
|||
|
items from one namespace, you can list them using curly braces and commas in
|
|||
|
the last position, like so:
|
|||
|
|
|||
|
```rust
|
|||
|
enum TrafficLight {
|
|||
|
Red,
|
|||
|
Yellow,
|
|||
|
Green,
|
|||
|
}
|
|||
|
|
|||
|
use TrafficLight::{Red, Yellow};
|
|||
|
|
|||
|
fn main() {
|
|||
|
let red = Red;
|
|||
|
let yellow = Yellow;
|
|||
|
let green = TrafficLight::Green; // because we didn't use TrafficLight::Green
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Glob Imports with `*`
|
|||
|
|
|||
|
If you'd like to import all the items in a namespace at once, you can use `*`:
|
|||
|
|
|||
|
```rust
|
|||
|
enum TrafficLight {
|
|||
|
Red,
|
|||
|
Yellow,
|
|||
|
Green,
|
|||
|
}
|
|||
|
|
|||
|
use TrafficLight::*;
|
|||
|
|
|||
|
fn main() {
|
|||
|
let red = Red;
|
|||
|
let yellow = Yellow;
|
|||
|
let green = Green;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
The `*` is called a 'glob', and it will import everything that's visible inside
|
|||
|
of the namespace. Globs should be used sparingly: they are convenient, but you
|
|||
|
might also pull in more things than you expected and cause naming conflicts.
|
|||
|
|
|||
|
### Using `super` to Access a Parent Module
|
|||
|
|
|||
|
Remember when we created our crate that Cargo made a `tests` module for us?
|
|||
|
Let's talk about that now. It was in `src/lib.rs`:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
pub mod client;
|
|||
|
|
|||
|
pub mod network;
|
|||
|
|
|||
|
#[cfg(test)]
|
|||
|
mod tests {
|
|||
|
#[test]
|
|||
|
fn it_works() {
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We'll explain more about testing in Chapter XX, but parts of this should make
|
|||
|
sense now: we have a module named `tests` that lives next to our other modules
|
|||
|
and contains one function named `it_works`. Even though there are special
|
|||
|
annotations, the `tests` module is just another module!
|
|||
|
|
|||
|
Since tests are for exercising the code within our library, let's try to call
|
|||
|
our `client::connect` function from this `it_works` function, even though
|
|||
|
we're not going to be checking any functionality right now:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
#[cfg(test)]
|
|||
|
mod tests {
|
|||
|
#[test]
|
|||
|
fn it_works() {
|
|||
|
client::connect();
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Run the tests by invoking the `cargo test` command:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo test
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
error[E0433]: failed to resolve. Use of undeclared type or module `client`
|
|||
|
--> src/lib.rs:9:9
|
|||
|
|
|
|||
|
9 | client::connect();
|
|||
|
| ^^^^^^^^^^^^^^^ Use of undeclared type or module `client`
|
|||
|
|
|||
|
warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
|||
|
--> src/network/server.rs:1:1
|
|||
|
|
|
|||
|
1 | fn connect() {
|
|||
|
| ^
|
|||
|
```
|
|||
|
|
|||
|
Why doesn't this compile? It's not because we don't have `communicator::` in
|
|||
|
front of the function like we had in `src/main.rs`: we are definitely within
|
|||
|
the `communicator` library crate here. The reason is that paths anywhere except
|
|||
|
in a `use` statement are relative to the current module (In a `use` statement,
|
|||
|
they're relative to the crate root by default). Our `tests` module doesn't have
|
|||
|
a `client` module in its scope!
|
|||
|
|
|||
|
So how do we get back up one module? We can either use leading colons to say
|
|||
|
that we want to start from the root and list the whole path:
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
::client::connect();
|
|||
|
```
|
|||
|
|
|||
|
Or we can use `super` to move up one module in the hierarchy:
|
|||
|
|
|||
|
```rust,ignore
|
|||
|
super::client::connect();
|
|||
|
```
|
|||
|
|
|||
|
If we were deep in the module hierarchy, starting from the root every time
|
|||
|
would get long. Plus, if we rearrange our modules by moving a subtree to
|
|||
|
another place, there might be a lot of places the path would need to be updated
|
|||
|
if we always used the path from the root.
|
|||
|
|
|||
|
It would also be annoying to have to type `super::` all the time in each test,
|
|||
|
but we now have a tool for that solution: `use`! `super::` is special and
|
|||
|
changes the path we give to `use` so that it is relative to the parent module
|
|||
|
instead of to the root module.
|
|||
|
|
|||
|
For these reasons, in the `tests` module especially, `use super::something` is
|
|||
|
usually the way to go. So now our test looks like this:
|
|||
|
|
|||
|
Filename: src/lib.rs
|
|||
|
|
|||
|
```rust
|
|||
|
#[cfg(test)]
|
|||
|
mod tests {
|
|||
|
use super::client;
|
|||
|
|
|||
|
#[test]
|
|||
|
fn it_works() {
|
|||
|
client::connect();
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If we run `cargo test` again, the test will pass and the first part of the test
|
|||
|
result output will be:
|
|||
|
|
|||
|
```bash
|
|||
|
$ cargo test
|
|||
|
Compiling communicator v0.1.0 (file:///projects/communicator)
|
|||
|
Running target/debug/communicator-92007ddb5330fa5a
|
|||
|
|
|||
|
running 1 test
|
|||
|
test tests::it_works ... ok
|
|||
|
|
|||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
|||
|
```
|
|||
|
|
|||
|
Now you know techniques for organizing your code! Use these to group related
|
|||
|
functionality together, keep files from getting too long, and present a tidy
|
|||
|
public API to users of your library.
|