Rust programming language 6

Posted by Pascal P. on Fri, 26 Nov 2021 10:13:38 +0100

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();
}

Topics: Rust