Rust organization and management
It is difficult to go deep into any programming language if it cannot organize the code, and almost no software product is compiled from a source file.
All the programs in this tutorial so far have been written in one file, mainly to facilitate learning the syntax and concepts of the Rust language.
For a project, organizing code is very important.
There are three important organizational concepts in Rust: boxes, packages, and modules.
Box (Crate)
A “box” is a binary program file or library file that exists in a “package”.
The “box” is a tree structure, and its tree root is the program compiled by the source file compiled when the compiler starts running.
Note: a “binary program file” is not necessarily a “binary executable file”. It can only be determined to be a file that contains the target machine language, and the file format varies depending on the compilation environment.
Package
When we use Cargo to execute new
command to create a Rust project, a Cargo.toml
files. The essence of the project is a package, which must becomposed of a Cargo.toml
file, which describes the basic information and dependencies of the package.
A package contains at most one library “box”, which can contain any numberof binary “boxes”, but at least one “box” (whether library or binary “box”).
When using cargo new
after the command has created the package src
directory will generate a main.rs
source file, Cargo default this file is the root of the binary box, the compiled binary box will be the same as the package name.
Module
For a software engineering, we often organize according to the organization specification of the programming language used, and the main structure of the organization module is often tree. The main unit of Java organization function module is class, while the main way of JavaScript organization module is function
.
The organizational units of these advanced languages can be contained in layers, just like the directory structure of the 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 procedure that describes a country under the rule of law: the nation includes the government, the congress and the court, which have executive, legislative and judicial functions, respectively. We can convert it into a tree structure:
nation
├── government
│ └── govern
├── congress
│ └── legislate
└── court
└── judicial
In the file system, the directory structure often uses a slash to indicate the location of the object in the path string, and the path separator in Rust is ::
.
Paths are divided into absolute paths and relative paths. Absolute path from crate
keyword begins to describe. Relative path from self
or super
keyword or an identifier begins to describe. For example:
crate::nation::government::govern();
Is a description. The govern
absolute path of the function, and the relative path can be expressed as:
nation::government::govern();
Now you can try to define a similar module structure in a source program anduse the path in the main function.
If you do this, you will surely find something wrong with it: government
modules and their functions are private, and you are not allowed to access them.
Access permission
There are two simple types of access in Rust: public and private.
By default, access to members in a module is private if no modifiers are added.
If you want to use public permissions, you need to use the pub
keyword.
For private modules, they can only be accessed at their level or subordinatelocation, not 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 be compiled. Please pay attention to observation court
in the module super
access method.
If a structure is defined in the module, its fields are private by default, in addition to being private. So if you want to use the structure in the module and its fields, you need to 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()
}
Running result:
I'd like Wheat toast please
Enumerated items of enumerated classes can contain fields, but do not have similar properties:
Example
mod SomeModule {
pub enum Person {
King {
name: String
},
Quene
}
}
fn main() {
let person = SomeModule::Person::King{
name: String::from("Blue")
};
match person {
SomeModule::Person::King {name} => {
println!("{}", name);
}
\_ => {}
}
}
Running result:
Blue
Modules that are hard to find
Developers who have used Java tend to hate the outermost class
block-its name is exactly the same as the file name, because it represents the file container, and although it is cumbersome, we have to write it again to emphasize that “this class is the class contained in the file.”
However, this has some advantages: at least it makes developers clearly aware of the existence of class wrappers, and can clearly describe the inheritance relationship of classes.
In Rust, a module is like a class wrapper in Java, but how to explain that you can write a main function at the beginning of a file?
The content of each Rust file is a module that is “hard to find”.
Let’s use two documents 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.")
}
Running result:
This is the main module.
This is the 2nd module.
Use keyword
use
keyword to introduce the module identifier into the current scope:
Example
mod nation {
pub mod government {
pub fn govern() {}
}
}
use crate::nation::government::govern;
fn main() {
govern();
}
This program can be compiled.
Because use
keyword handle the govern
identifier is imported into the current module and can be used directly.
This solves the problem that the local module path is too long.
Of course, there are cases where two identical names also need to be imported, and 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();
}
There are two here. govern
function, one is nation
, the next one is government
, we’ll use as
, set nation
to give me an alias under nation_govern
. Two names can be used simultaneously.
use
keyword can be associated with the pub
keyword in conjunction with:
Example
mod nation {
pub mod government {
pub fn govern() {}
}
pub use government::govern;
}
fn main() {
nation::govern();
}
Reference standard library
Rust official Standard Library Dictionary: https://doc.rust-lang.org/stable/std/all.html
After learning the concepts in this chapter, we can easily import system libraries to facilitate the development of programs:
Example
use std::f64::consts::PI;
fn main() {
println!("{}", (PI / 2.0).sin());
}
Running result:
1
All system library modules are imported by default, so you only need to use the use
keyword simplifies the path and is easy to use.