Rust Organization Management

Any programming language that cannot organize code will be difficult to master; almost no software product is compiled from a single source file.

Up to this point in this tutorial, all programs have been written in a single file, mainly to make it convenient to learn Rust's syntax and concepts.

For a project, organizing code is very important.

Rust has three important organizational concepts: crate, package, and module.

Crate

A "crate" is a binary program file or a library file, and it exists in a "package".

A "crate" has a tree-like structure, and its root is the program compiled from the source file that the compiler compiles when it starts running.

Note: A "binary program file" is not necessarily a "binary executable file"; it can only be confirmed that it is a file containing target machine language, and the file format varies depending on the compilation environment.

Package

When we use Cargo to execute the new command to create a Rust project, a Cargo.toml file is created in the project directory. The essence of a project is a package. A package must be managed by a Cargo.toml file, which describes the package's basic information and dependencies.

A package can contain at most one library "crate", can contain any number of binary "crates", but must contain at least one "crate" (whether it is a library or a binary "crate").

After creating a package with the cargo new command, a main.rs source file is generated in the src directory. Cargo treats this file as the root of the binary crate by default, and the compiled binary crate will have the same name as the package.

Module

For a software project, we often organize it according to the organizational conventions of the programming language used. The main structure for organizing modules is often a tree. The main unit for organizing functional modules in Java is the class, while the main way JavaScript organizes modules is through functions.

The organizational units of these advanced languages can be nested layer by layer, just like the directory structure of a file system. The organizational unit in Rust is the module (Module).

mod nation {
    mod government {
        fn govern() {}
    }
    mod congress {
        fn legislate() {}
    }
    mod court {
        fn judicial() {}
    }
}

This is a program describing a country governed by the rule of law: the nation includes the government, congress, and court, which have executive, legislative, and judicial functions respectively. We can convert this into a tree structure:

nation
 ├── government
 │ └── govern
 ├── congress
 │ └── legislate
 └── court
   └── judicial

In a file system, the directory structure often uses slashes in path strings to represent the position of objects. The path separator in Rust is:: 。

Paths are divided into absolute paths and relative paths. Absolute paths are described starting from the crate keyword. Relative paths are described starting from the self or super keyword, or from an identifier. For example:

crate::nation::government::govern();

is the absolute path describing the govern function. The relative path can be expressed as:

nation::government::govern();

Now you can try defining a similar module structure in a source program and using paths in the main function.

If you do this, you will definitely find something wrong with it: the government module and the functions in it are all private, and you are not allowed to access them.


Access Permissions

Rust has two simple access permissions: public and private.

By default, if no modifier is added, the access permission of members in a module will be private.

If you want to use public permission, you need to use the pub keyword.

For private modules, they can only be accessed from positions at the same level or lower levels; they cannot be accessed from outside.

Example

mod nation {
    pub mod government {
        pub fn govern() {}
    }

    mod congress {
        pub fn legislate() {}
    }
   
    mod court {
        fn judicial() {
            super::congress::legislate();
        }
    }
}

fn main() {
    nation::government::govern();
}

This program can pass compilation. Please pay attention to how super is accessed in the court module.

If a struct is defined in a module, besides the struct itself being private, its fields are also private by default. Therefore, if you want to use a struct and its fields from a module, you need the pub declaration:

Example

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}
pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);
}
fn main() {
    eat_at_restaurant()
}

Output:

I'd like Wheat toast please

Enum variants can contain fields, but they do not have a similar property:

Example

mod SomeModule {
    pub enum Person {
        King {
            name: String
        },
        Queen
    }
}

fn main() {
    let person = SomeModule::Person::King{
        name: String::from("Blue")
    };
    match person {
        SomeModule::Person::King {name} => {
            println!("{}", name);
        }
        _ => {}
    }
}

Output:

Blue

Hidden Modules

Developers who have used Java often dislike the outermost class block when programming — its name is exactly the same as the file name, because it represents the file container. Although it is cumbersome, we have to write it once to emphasize that "this class is the class contained in the file".

However, this has some benefits: at least it makes developers clearly aware of the existence of class wrapping, and it can clearly describe class inheritance relationships.

In Rust, modules are like class wrappers in Java, but a main function can be written at the very beginning of a file. How can this be explained?

The content of every Rust file is a "hidden" module.

Let us use two files to reveal this:

main.rs file

// main.rs
mod second_module;

fn main() {
    println!("This is the main module.");
    println!("{}", second_module::message());
}

second_module.rs file

// second_module.rs
pub fn message() -> String {
    String::from("This is the 2nd module.")
}

Output:

This is the main module.
This is the 2nd module.

use Keyword

The use keyword can bring module identifiers into the current scope:

Example

mod nation {
    pub mod government {
        pub fn govern() {}
    }
}

use crate::nation::government::govern;

fn main() {
    govern();
}

This program can pass compilation.

Because the use keyword imports the govern identifier into the current module, it can be used directly.

This solves the problem of overly long local module paths.

Of course, in some cases there are two identical names that also need to be imported. We can use the as keyword to add an alias to the identifier:

Example

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub fn govern() {}
}
   
use crate::nation::government::govern;
use crate::nation::govern as nation_govern;

fn main() {
    nation_govern();
    govern();
}

Here there are two govern functions, one under nation and one under government. We use as to alias the one under nation as nation_govern. Both names can be used simultaneously.

The use keyword can be used together with the pub keyword:

Example

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    pub use government::govern;
}

fn main() {
    nation::govern();
}

Referencing the Standard Library

Rust official standard library documentation:https://doc.rust-lang.org/stable/std/all.html

After learning the concepts in this chapter, we can easily import system libraries to develop programs conveniently:

Example

use std::f64::consts::PI;

fn main() {
    println!("{}", (PI / 2.0).sin());
}

Output:

1

All system library modules are imported by default, so when using them, you only need to use the use keyword to simplify paths for convenient use.

Other Extensions