第六章
- Packages: A Cargo feature that lets you build, test, and share crates
- Crates: A tree of modules that produces a library or executable
- Modules and use: Let you control the organization, scope, and privacy of paths
- Paths: A way of naming an item, such as a struct, function, or module
Packages and Crates
The crate root is a source file that the Rust compiler starts from and makes up the root module of your crate. A package is one or more crates that provide a set of functionality. A package contains a Cargo.toml file that describes how to build those crates.
Several rules determine what a package can contain. A package must contain zero or one library crates, and no more. It can contain as many binary crates as you’d like, but it must contain at least one crate (either library or binary).
A package contains:
- at least one crate (either library or binary);
- at most one library crate.
- src/main.rs is the crate root of a binary crate with the same name as the package.
- src/lib.rs is its crate root of of a library crate with the same name as the package.
If a package contains src/main.rs and src/lib.rs, it has two crates: a library and a binary, both with the same name as the package. A package can have multiple binary crates by placing files in the src/bin directory: each file will be a separate binary crate.
A crate will group related functionality together in a scope so the functionality is easy to share between multiple projects. For example, the rand
crate we used in Chapter 2 provides functionality that generates random numbers. We can use that functionality in our own projects by bringing the rand
crate into our project’s scope. All the functionality provided by the rand
crate is accessible through the crate’s name, rand
.
Keeping a crate’s functionality in its own scope clarifies whether particular functionality is defined in our crate or the rand
crate and prevents potential conflicts. For example, the rand
crate provides a trait named Rng
. We can also define a struct
named Rng
in our own crate. Because a crate’s functionality is namespaced in its own scope, when we add rand
as a dependency, the compiler isn’t confused about what the name Rng
refers to. In our crate, it refers to the struct Rng
that we defined. We would access the Rng
trait from the rand crate as rand::Rng
.
Defining Modules to Control Scope and Privacy
Modules let us organize code within a crate into groups for readability and easy reuse. Modules also control the privacy of items, which is whether an item can be used by outside code (public) or is an internal implementation detail and not available for outside use (private).
module tree
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
Paths for Referring to an Item in the Module Tree
- An absolute path starts from a crate root by using a crate name or a literal
crate
. - A relative path starts from the current module and uses
self
,super
, or an identifier in the current module.
Both absolute and relative paths are followed by one or more identifiers separated by double colons (::
).
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
Choosing whether to use a relative or absolute path is a decision you’ll make based on your project. The decision should depend on whether you’re more likely to move item definition code separately from or together with the code that uses the item. For example, if we move the front_of_house
module and the eat_at_restaurant
function into a module named customer_experience
, we’d need to update the absolute path to add_to_waitlist
, but the relative path would still be valid. However, if we moved the eat_at_restaurant
function separately into a module named dining, the absolute path to the add_to_waitlist
call would stay the same, but the relative path would need to be updated. Our preference is to specify absolute paths because it’s more likely to move code definitions and item calls independently of each other.
Modules aren’t useful only for organizing your code. They also define Rust’s privacy boundary: the line that encapsulates the implementation details external code isn’t allowed to know about, call, or rely on. So, if you want to make an item like a function or struct private, you put it in a module.
The way privacy works in Rust is that all items (functions, methods, structs, enums, modules, and constants) are private by default. Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules. The reason is that child modules wrap and hide their implementation details, but the child modules can see the context in which they’re defined.
Rust chose to have the module system function this way so that hiding inner implementation details is the default. That way, you know which parts of the inner code you can change without breaking outer code. But you can expose inner parts of child modules’ code to outer ancestor modules by using the pub
keyword to make an item public.
Exposing Paths with the pub Keyword
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
Starting Relative Paths with super
We can also construct relative paths that begin in the parent module by using super
at the start of the path. This is like starting a filesystem path with the ..
syntax.
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
Making Structs and Enums Public
Enums aren’t very useful unless their variants are public; it would be annoying to have to annotate all enum variants with pub
in every case, so the default for enum variants is to be public. Structs are often useful without their fields being public, so struct fields follow the general rule of everything being private by default unless annotated with pub
.
Bringing Paths into Scope with the use Keyword
Adding use
and a path in a scope is similar to creating a symbolic link in the filesystem.
You can also bring an item into scope with use
and a relative path.
Creating Idiomatic use Paths
Bringing the function’s parent module into scope with use
so we have to specify the parent module when calling the function makes it clear that the function isn’t locally defined while still minimizing repetition of the full path.
On the other hand, when bringing in structs, enums, and other items with use
, it’s idiomatic to specify the full path.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
Providing New Names with the as
Keyword
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
Re-exporting Names with pub
use
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
By using pub use
, external code can now call the add_to_waitlist
function using hosting::add_to_waitlist
. If we hadn’t specified pub use
, the eat_at_restaurant
function could call hosting::add_to_waitlist
in its scope, but external code couldn’t take advantage of this new path.
Using External Packages
Using Nested Paths to Clean Up Large use Lists
If we’re using multiple items defined in the same package or same module, listing each item on its own line can take up a lot of vertical space in our files.
Instead, we can use nested paths to bring the same items into scope in one line. We do this by specifying the common part of the path, followed by two colons, and then curly brackets around a list of the parts of the paths that differ.
We can use a nested path at any level in a path, which is useful when combining two use statements that share a subpath.
use std::io;
use std::io::Write;
use std::io::{self, Write};
The Glob Operator
If we want to bring all public items defined in a path into scope, we can specify that path followed by *
, the glob operator:
use std::collections::*;
Separating Modules into Different Files
Using a semicolon after mod front_of_house
rather than using a block tells Rust to load the contents of the module from another file with the same name as the module.
Rust lets you split a package into multiple crates and a crate into modules so you can refer to items defined in one module from another module. You can do this by specifying absolute or relative paths. These paths can be brought into scope with a use statement so you can use a shorter path for multiple uses of the item in that scope. Module code is private by default, but you can make definitions public by adding the pub keyword.