Manage projects using packages, crates and modules (Part 2)

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 useThis 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::iothat 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.

Guess you like

Origin blog.csdn.net/u014388408/article/details/134921511