title: Rust programming language (6)
date: 2021-11-26
updated: 2021-11-26
comments: true
toc: true
Execerpt: the package of rust, used by crite and module
tags:
- Rust
categories: - programming
preface
People who have really written the project know that the code can't be handled by a file. The logic inside is very complex, so the projects have a directory structure. We separate them according to the logical classification in order to improve the readability of the code
With the increase of the project, we will add the concept of multiple packages. A package can contain multiple binary crite items and an optional crite library. As the package becomes larger and larger, we can extract part of the package code into independent crite and external dependencies, and make the code available by reference. This article will talk about how to use these, T rust has a concept of namespace, which will be answered later
Generally, we will separate reusable logic and code, package them into separate packages, and expose the interface to others
There will also be a concept of scope. The context where the code is located has a set of names defined as in scope. When reading, writing and compiling code, programmers and compilers need to know whether the specific name of a specific location refers to variables, functions, structures, enumerations, modules, constants or other meaningful items. You can create a scope, And change which names are inside or outside the scope. The same scope cannot have two items with the same name at the same time
T rust has many functions that allow you to manage the organization of code, including what can be exposed, which are private parts, and the name of each scope of the program. These functions, also known as the module system, include several parts
- Packages: a function of cargo that allows you to build, test and analyze crate s
- Crites: a module tree structure that forms a library or binary project
- Modules and use: let you control the privacy of scopes and paths
- Path: the way to name structures, functions, or modules
Packages and crate s
Crate is a binary or library. Crate root is the beginning of everything. The compiler thinks it is the starting point and constitutes your crate root module
A package is one or more crates that provide a series of functions. A package will contain a cargo.toml file that describes how to build these crates
A package can contain at most one library crite, and can contain any binary crite. There is at least one crite in the package, whether it is a library crite or a binary crite
We use cargo new package_name to create the package. It is found that several default files will be generated
➜ student cargo new package_modules Created binary (application) `package_modules` package ➜ student cd package_modules ➜ package_modules git:(master) ✗ tree . ├── Cargo.toml └── src └── main.rs 1 directory, 2 files
We can see that the main.rs file will be generated in the src folder. In fact, trust thinks that main.rs is the root of binary crite, that is, crite root. However, when there is a lib.rs under src, trust thinks that the package has a library crite with the same name, and lib.rs is the root of crite
Come back to us, this is the package_ There is only one binary crite under name. When we add lib.rs under src, there is a binary crite and a library crite
If multiple files are put into src/bin, there will be multiple binary crates, and each file will be compiled into binary crates separately
A crate usually represents a collection of function blocks and can be easily shared with others or other packages. We used rand to generate random numbers before. rand is a crate. We introduce the crates written by others into our own use. When we use rand, we need to access it through its name rand
For the scope, the internal features of a crite (such as rand's Rng) will take effect internally. When other projects reference crite, the added crite has feature a, and their own projects also have feature A. they will not be confused, because the name of the crite (such as rand::Rng) must be added to the reference crite
Define modules to control scope
If we need to write a crite for other projects, we need to consider what functions or features to provide to each other. Our own internal variables and functions certainly don't want to be directly accessible outside. We want to distinguish between private and public things
The module allows us to group the code of crate. First, it will improve readability and reusability. In addition, it can control the privacy of items, and divide items into private items that can not be used externally and public items that can be used externally
We use the new cargo instruction to create a new library named restaurant, crash, cargo new -- lib restaurant
➜ student cargo new --lib restaurant Created library `restaurant` package ➜ student cd restaurant ➜ restaurant git:(master) ✗ tree . ├── Cargo.toml └── src └── lib.rs 1 directory, 2 files
Using the method of creating a new library crite, it is found that the lib.rs flag is no longer generated. This is a library crite
We modify the code of lib.rs to
mod front_of_house { // mod defines a module, followed by the module name front_of_house // Content of the module mod hosting { // Define module front_ of_ The module hosting under house fn add_to_waitlist() {} // Functions under module hosting fn seat_at_table() {} } mod serving { // Define module front_ of_ The module serving under house fn take_order() {} fn server_order() {} fn take_payment() {} } }
The mod keyword is used to define the module, followed by the module name. The specific content of the module is defined in {} and two sub modules are defined above. Three functions are defined in the sub module. Modules, functions, structures, enumerations, constants and features can be defined in the module
The module can put the relevant definitions and code together to improve the readability of the code, and the effect is better when used with comments
As mentioned above, the root of crate may be src/main.rs or src/lib.rs. in fact, these two files automatically generate a root module named crate in the crate module structure, which is called module tree
For example, in the above code, the structure of the module tree is
crate └── front_of_house ├── hosting │ ├── add_to_waitlist │ └── seat_at_table └── serving ├── take_order ├── serve_order └── take_payment
Since it is a tree structure, there must be a parent-child relationship and a peer sibling relationship. It should be noted that the top crate is implicitly generated, so it is called root
The path of the item in the module
If we want to reference an item in the module, we need to know its path. There are two paths: relative path and absolute path
- Absolute path: find the item layer by layer starting from the root of crate
- Relative path: starting from the current module, it starts with self, super or the identifier of the current module
No matter what path it is, it will be followed by one or more identifiers separated by double colons (::)
We modified the previous module. For the convenience of demonstration, we removed redundant modules and added reference methods
mod front_of_house { // mod defines a module, followed by the module name front_of_house // Content of the module mod hosting { // Define module front_ of_ The module hosting under house fn add_to_waitlist() {} // Functions under module hosting } } fn eat_at_restaurant() { // pub is the keyword, which means that this function is public // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Absolute address reference module front_ of_ Add of hosting module of house_ to_ Waitlist function // Relative path front_of_house::hosting::add_to_waitlist(); // Relative address reference }
The keyword pub identifies that this item is public and can be called externally
For absolute address references, start with crate, because the most rooted module is crate, and then address step by step according to the module definition tree structure. In the middle of each level, use:: connection
Relative to the address, because front_of_house and eat_ at_ Restaurants are at the same level, so there is no need to add crate, because they all belong to the largest crate module
The absolute path and relative path are more appropriate. In fact, there are no special requirements. Consider it from your perspective (the document says that if the module structure changes, the path needs to be modified if it is a relative path, so the absolute path is recommended)
Because it is a crate library, we should use cargo build instead of cargo run
➜ restaurant git:(master) ✗ cargo build Compiling restaurant v0.1.0 (/Users/Work/Code/Rust/student/restaurant) error[E0603]: module `hosting` is private
We can find that errors will occur during compilation, indicating that the hosting module is private
As we said before, modularity can sort and classify items, and set private and public
For various privatized items, external code is not allowed to call, so as to avoid exposing the items inside crite to the outside during reference. For items that are not specified as private or public, they are private by default
- The parent module can only introduce the exposed items of the child module
- Child modules can access all items of the parent module
- Sibling modules can directly access all items from each other
Use pub to expose items
As mentioned earlier, the keyword pub can set an item to be public
We modified the previous code
mod front_of_house { // mod defines a module, followed by the module name front_of_house // Content of the module pub mod hosting { // Define module front_ of_ The module hosting under house is set to public by pub pub fn add_to_waitlist() {} // The function under module hosting. pub sets it to public } } fn eat_at_restaurant() { // pub is the keyword, which means that this function is public // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Absolute address reference module front_ of_ Add of hosting module of house_ to_ Waitlist function // Relative path front_of_house::hosting::add_to_waitlist(); // Relative address reference }
Special attention should be paid here if you don't add_ to_ If the waitlist is set to public, setting only hosting is not enough, because setting only the module to public does not mean that all items in it are public. You need to access add_to_waitlist function, so the function item also needs to set pub. When you do not set hosting, only add is set_ to_ Neither can waitlist
➜ restaurant git:(master) ✗ cargo build Compiling restaurant v0.1.0 (/Users/Work/Code/Rust/student/restaurant) Finished dev [unoptimized + debuginfo] target(s) in 0.23s
super to set the relative path
There is also a way to find relative paths starting with super. See the following example
fn serve_order() {} // Function serve_order mod back_of_house { // Module back_of_house fn fix_incorrect_order() { // Function fix_incorrect_order cook_order(); // Reference function cook_order super::serve_order(); // Relative reference serve_order (super mode) } fn cook_order() {} // Function cook_order }
super is equivalent to.. in the linux path, which means the upper level of the current level. The upper level here is actually crite
Public structure and enumeration
For a structure, if pub is set for the structure, it only means that the structure itself becomes public. The following fields are private by default. You need to specify pub separately
mod back_of_house { pub struct Breakfast { // Set exposed structure pub toast: String, // Set the toast exposure of the field of the structure seasonal_fruit: String, // Do not set default not public } impl Breakfast { pub fn summer(toast: &str) -> Breakfast { // The method of setting the structure is exposed by summer Breakfast { toast: String::from(toast), seasonal_fruit: String::from("peaches"), } } } } pub fn eat_at_restaurant() { // Order a breakfast in the summer with Rye toast let mut meal = back_of_house::Breakfast::summer("Rye"); println!("{}", meal.toast); // Change our mind about what bread we'd like meal.toast = String::from("Wheat"); println!("I'd like {} toast please", meal.toast); println!("{}", meal.seasonal_fruit); // No, because it's not public }
For enumeration, if you publish an enumeration item, all members under the item are public
mod back_of_house { pub enum Appetizer { // Exposed enumerations Soup, // open Salad, // open } } pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad; }
This is considered from the use scenario. Enumeration is usually used as multiple possible results, so you may need all public, and it is relatively cumbersome to write pub for each
For structures, it is possible that not all fields need to be disclosed. If all fields are disclosed but some are not used, the code quality will be reduced
Use the use keyword to bring a name into the scope
When we refer to items above, we must write a long path when using them. When using use, we can only introduce them once, and then we can use the imported items as when using our own items
mod front_of_house { // modular pub mod hosting { // Public module pub fn add_to_waitlist() {} // Public function } } use crate::front_of_house::hosting; // Introducing hosting pub fn eat_at_restaurant() { // Various items under calling hosting hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }
After using use to reference the required items, you can directly use the items. Of course, the private and public rules are consistent with the previous reference methods
Relative paths are also possible
mod front_of_house { // modular pub mod hosting { // Public module pub fn add_to_waitlist() {} // Public function } } use front_of_house::hosting; // Introducing hosting pub fn eat_at_restaurant() { // Various items under calling hosting hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }
The usual way of using
When referring to a function item, do not refer directly to the function
In the above code, we use use use import, which is to introduce use front_of_house::hosting and then use hosting:: add_ when calling. to_ waitlist()
Some people may ask, why not use use front when referencing_ of_ house::hosting::add_ to_ Waitlist directly adds when used_ to_ What about waitlist()? In fact, it can be run, but it does not conform to the usage habit. We hope to keep the upper level as much as possible when using external function items, so as to distinguish them from local items
When you reference another item, you reference it directly
Non functional items are usually directly referenced to this item. Everyone writes this
However, there may be exceptions. For example, two different Crites have the same item, such as
use std::fmt; use std::io; fn function1() -> fmt::Result { // Result // --snip-- } fn function2() -> io::Result<()> { // Result // --snip-- }
At this time, it still needs to be referenced to the upper level, because t rust can't distinguish which item is because their names are the same
Rename using as
For the problem of referencing multiple crate s but the items in them are consistent, you can also use the as keyword to rename an item
use std::fmt::Result; use std::io::Result as IoResult; // Renamed as ioresult in this crate fn function1() -> Result { // --snip-- } fn function2() -> IoResult<()> { // --snip-- }
Re export items using pub use
For our own crite, the public items we import from others will become our own private items. If we want to make the imported items public in our crite, we can use pub use
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting; // Re export. At this time, the code that references our crite can also reference hosting pub fn eat_at_restaurant() { // You can use front for your own crite_ of_ Of house:: Hosting hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }
When other code is introduced into our crite, you can use hosting::add_to_waitlist to introduce the function add_to_waitlist, and this function is also referenced by our crate
Using external packages
Like python's package repository pypi, t rust also has its own software repository crates.io: Rust Package Registry , we have used the above package rand before by adding rows in Cargo.toml
[dependencies] rand = "0.5.5"
Add the rand dependency in Cargo.toml and tell cargo to download from crites.io and use it in the project code
Then use use to reference these items, such as
use rand::Rng; fn main() { let secret_number = rand::thread_rng().gen_range(1, 101); }
Note that the standard library (std) is also an external crite for your package and needs to be imported. However, it is too often used, so it is integrated into Rust. You do not need to modify Cargo.toml to import std, but you need to import the items defined in the standard library into the scope of the project package through use to reference them. For example, the HashMap we use:
use std::collections::HashMap;
Reduce a large number of use lines by nesting paths
use keyword can only be written in one line, that is, now you can only introduce one item in one line. When we have many items, there will be many line problems, such as
use std::cmp::Ordering; use std::io;
They all belong to std. at this time, we can merge the two
use std::{cmp::Ordering, io};
Use {} to introduce multiple paths, provided that they are all the same prefix, and each path is divided by
When it is necessary to introduce a parent path and a child path, you can use self to represent the prefix itself
use std::io; use std::io::Write;
Change to
use std::io::{self, Write};
Bring in all public items through golobal
We can also import all public items of a path through the keyword golobal
use std::collections::*;
This is not recommended because you can easily confuse the public items under it with your own items, and you will not use all the path public items you usually introduce
Split modules into different files
So far, the modules in the above example are in a file lib.rs, but in actual development, the modules are actually large, and we need to separate them to improve the readability of the code
Let's create a new src/front_of_house.rs file and write code
pub mod hosting { // Defining module hosting pub fn add_to_waitlist() {} // Public function }
Then modify the src/lib.rs code
mod front_of_house; // Import front under the same level directory_ of_ Add in module in house.rs pub use crate::front_of_house::hosting; // Introducing sub module hosting pub fn eat_at_restaurant() { // Public function hosting::add_to_waitlist(); // Call add of hosting_ to_ Waitlist function hosting::add_to_waitlist(); hosting::add_to_waitlist(); }
At this time, the files under src are
➜ src git:(master) ✗ tree . ├── front_of_house.rs └── lib.rs 0 directories, 2 files
In mod front_ of_ Use a semicolon after house instead of a code block, which will tell Rust to load the contents of the module in another file with the same name as the module. Here is the front_ of_ The hosting of house.rs is imported
Sometimes we need to add more directories to express more complex relationships, for example
New src/front_of_house directory, write src/front_of_house/hosting.rs file
pub fn add_to_waitlist() {}
Modify file src/front_of_house.rs
pub mod hosting; // The name of the imported peer is front_ of_ Item of the hosting.rs file under the house folder
The file structure under src is
➜ src git:(master) ✗ tree . ├── front_of_house │ └── hosting.rs ├── front_of_house.rs └── lib.rs 1 directory, 3 files
As mentioned in the above note, when you find mod xxx under the RS file of crate; It means that he will introduce the items in the xxx.rs file under the folder with the same file name at the same level
At the beginning, I didn't understand why it was written like this. There was no relatively detailed introduction in the official documents. I only found a section saying it should be written like this. See: Modules - The Rust Reference (rust-lang.org) In short, just find the contents of the folder with the same level and the same name
The same is true for multilayer structures
Modify src/lib.rs
mod front_of_house; // Introduce front_ of_ Items in the house.rs file pub use crate::front_of_house::hosting; pub use crate::front_of_house::hosting::test; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); test::t(); }
Modify src/front_of_house/hosting.rs
pub mod test; pub fn add_to_waitlist() {}
New folder src/front_of_house/hosting, modify the file src/front_of_house/hosting/test.rs
pub fn t(){}
His directory is
➜ src git:(master) ✗ tree . ├── front_of_house │ ├── hosting │ │ └── test.rs │ └── hosting.rs ├── front_of_house.rs └── lib.rs 2 directories, 4 files
You can see that pub mod test is added in hosting.rs; It means to go to the directory at the same level as him, that is, to find the test.rs file in hosting, find the items in it, and find the function t
Using hosting::test::t() in lib.rs; This function item can be called
We can also modify the reference of lib.rs by reducing use during nesting
mod front_of_house; // Introduce front_ of_ Items in the house.rs file pub use crate::front_of_house::hosting::{self, test}; // Introducing hosting and hosting::test pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); test::t(); }