1. Use the use keyword to introduce the path into the scope
In the previous example, when we referenced functions or structures in the module, we needed to use relative paths or absolute paths to reference them. However, here, there is a way to simplify this process. We can use use
keywords to create a short path and then use this shorter name anywhere in the scope.
An example looks like this:
pub mod people {
pub enum Sex {
Man,
Woman,
}
}
use crate::people::Sex;
fn get_sex() {
let man = Sex::Man;
}
Adding a path to a scope use
is similar to creating a symbolic link in the file system. By adding use
crate::people::Sex to the crate root, Sex is now a valid name in the scope, as if the Sex type was defined in the crate root. Paths introduced through use
scope are also checked for privacy, just like other paths.
Note that use
only use
short paths within the specific scope can be created. Let's take a look at the following example.
pub mod people {
pub enum Sex {
Man,
Woman,
}
}
use crate::people::Sex;
mod ceshi {
fn get_sex() {
let man = Sex::Man;
}
}
Run the following and see the corresponding error message:
As shown in the above figure: we can know that the type Sex has not been declared, so the scope declared by use is different from the scope where the current method is located. If you reference in the current method, there are two ways: the first is to reference through the super keyword, the value defined by use is in the parent of the current method, the second is to move the use statement to the current module. .
// 第一种方式
use crate::people::Sex;
mod ceshi {
fn get_sex() {
let man = super::Sex::Man;
}
}
// 第二种方式
mod ceshi {
use crate::people::Sex;
fn get_sex() {
let man = Sex::Man;
}
}
1.1 Create a customary use path
In the previous example, when we used use, we directly referenced the specific structure type definition. use
When using to introduce structures, enumerations, and other items, it is customary to specify their full paths. , an example is as follows:
pub mod root {
pub mod people {
pub enum Sex {
Man,
Woman,
}
}
}
use crate::root::people::Sex;
fn get_sex() {
let man = Sex::Man;
}
If it is a function type, we must specify the parent module when calling the function. This can clearly indicate that the function is not defined locally and minimize the duplication of the complete path. An example looks like this:
pub mod root {
pub mod people {
pub fn getPeo() {}
}
}
use crate::root::people;
fn get_sex() {
let man = people::getPeo;
}
There is no hard and fast requirement behind this idiom: it's just a convention, and people are used to reading and writing Rust code this way.
The one exception to this idiom is when we want to use use
a statement to bring two items with the same name into scope, since Rust doesn't allow this.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
As you can see, the two Result
types can be distinguished using the parent module. If we specify use std::fmt::Result
and use std::io::Result
, we'll have two Result
types in the same scope, and when we use them Result
, Rust won't know which one we're using. This means that when we specify a specific type, the probability of having the same name may be too high, so we can just refer to its parent to distinguish it.
1.2 Use the as keyword to provide a new name
There use
is another solution to the problem of bringing two types with the same name into the same scope: after the path of the type, we as
specify a new local name or alias using . An example is shown below:
use std::fmt::Result;
use std::io::Result as ResultAli;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> ResultAli<()> {
// --snip--
Ok(())
}
1.3 Use pub use to re-export the name
Using use
keywords, after a name is imported into the current scope, the name can be used within this scope, but it remains private outside this scope. If we want others to be able to use this name normally when calling our code, as if it were in the current scope, then we can use pub
and use
This technique is called " re -exporting " : we not only import a name into the current scope, but we also allow others to import it into their own scopes.
An example looks like this:
pub mod root {
pub mod people {
pub fn getPeo() {}
}
}
pub use crate::root::people; // 重导出
fn get_sex() {
let man = people::getPeo;
}
Before this change, external code needed to use the path my_project::root::people::getPeo() to call the getPeo() function. Now that this pub use
re-exports the people module from the root module, external code can now use the path restaurant::peopel::getPeo
.
1.4 Using external packages
When using an external package, add the corresponding package name and corresponding version number to Cargo.toml , as shown below:
rand = "0.8.5"
Adding dependencies to Cargo.toml tellsrand
Cargo to download its dependencies from crates.iorand
and make them available in the project code.
Next, to rand
bring the definition into the scope of the project package, we add a line starting use
with the rand
package name and listing the items that need to be brought into scope. Examples are as follows:
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..100);
println!("res {}", secret_number)
}
After running cargo run, you will find that the corresponding resources are downloaded, and then a random number between 1 and 100 is printed as follows:
1.5 Nest paths to eliminate large use lines
When many items defined in the same package or module need to be introduced, listing a separate line for each item will take up a lot of space in the source code. An example looks like this:
use std::cmp::Ordering;
use std::io;
Instead, we can use nested paths to bring the same item into scope in one line. To do this, specify the same part of the path, followed by two colons, and then the different parts of the path in curly braces, as shown below:
use std::{cmp::Ordering, io};
use
We can use nested paths at any level of the path, which is useful when combining two statements that share subpaths . For example:
use std::io;
use std::io::Write;
The common part of both paths is std::io
that this is exactly the first path. In order to use
introduce both paths in a single statement, you can use nested paths self
, an example is as follows:
use std::io::{self, Write};
1.6 Bring all public definitions into scope through the glob operator
If you want to bring all public items under a path into scope, you can specify the path followed *
by the glob operator:
use std::collections::*;
This use
statement std::collections
brings all public items defined in into the current scope. Please be careful when using the glob operator! Globs can make it difficult to deduce what names are in scope and where they are defined.
2. Split the module into multiple files
The previous examples all define multiple modules in one file. As modules get larger, you may want to move their definitions into separate files to make the code easier to read.
For example, the following code in the previous src/lib.rs
pub mod garden {
pub mod vegetables {
#[derive(Debug)]
pub struct Asparagus {
pub color: String,
pub number: i32,
}
}
}
Then rewrite the contents of the src/lib.rs file as follows:
mod garden;
use garden::vegetables::Asparagus;
pub fn getAsparagus() {
let res = Asparagus {
color: String::from("red"),
number: 12,
};
}
The first line declares mod garden; search in the src/garden.rs file. The content is as follows:
pub mod vegetables;
Then we create a src/garden directory and a vegetables.rs file containing the definition of the Asparagus structure : the file content is as follows:
pub struct Asparagus {
pub color: String,
pub number: i32,
}
The directory structure is as follows:
my-project
├─ Cargo.lock
├─ Cargo.toml
├─ README.md
└─ src
├─ garden
│ └─ vegetables.rs
├─ garden.rs
├─ lib.rs
└─ main.rs
Summarize:
There are generally two ways to reference modules:
The first is src/garden/vegetables.rs shown above
The second method is src/garden.rs, and the vegetable module is written in the current file.
If you use both path styles for the same module, you will get a compilation error. Mixing different path styles between different modules in the same project is allowed, but this may confuse others.
The main disadvantage of using the mod.rs file name style is that it results in many mod.rs files in the project, which can be confusing when you open them all at the same time in the editor.
We moved the code of each module to a separate file, while the module tree remains the same. This provides stronger decoupling and makes it easier to migrate and reconstruct subsequent modules.