When learning a programming language, the focus is often prioritized on the language's syntax and standard libraries, in the hope of being able to develop in the new language as soon as possible.
I'm the same way when I'm learning a new development language myself.
However, if you want to develop actual projects in a new language, or make some gadgets of your own, in addition to the language itself, the
Understanding how it organizes the code in a project is also crucial.
After all, it is not possible to have only one code file in a real project, as is often the case when learning a language.
This article focuses onRust
common way of organizing code in a language, I use theRust
The time is not long, so please correct me if I am wrong.
1. Module on management code
Rust
Three code management units are provided at the language level, namely:
-
module
: Modules, similar to namespaces in other languages, generally encapsulate the implementation of a specific function. -
crate
:Rust
The smallest unit of compilation, eachcrate
There is afile to configure the
crate
Relevant information.A `crate` can be a library or a binary.
-
package
: apackage
can be considered a project.cargo new
command creates thepackage
。
The relationship between the three is that a package can contain multiple crates and a crate can contain multiple modules.
For a typical project, a common form of code organization would beOne crate + multiple modules。
If the functionality is complex and there are many businesses, you can encapsulate the separate functions or businesses into thecrate
Turn it into apackage
embodyMultiple crates + multiple modules。
For example, the officially recommended project for learning Rust, ripgrep(/BurntSushi/ripgrep)。
just as muchcrate
of the program, eachcrate
have their ownConfiguration.
2. Modules
package
,crate
cap (a poem)module
Of the three ways to organize code, the most common is themodule
。
The following is the main introductionRust
centermodule
The way it is defined and referenced.
First, let's create a demo project.
$ cargo new myproj
Creating binary (application) `myproj` package
The default project has the following directory structure:
2.1 Modules in the same document
Rust
modules do not have to be defined in a separate file; several different modules can be defined in the same file.
This is different from some languages that distinguish modules by files and directories.
For example, the following is in theTwo modules are defined in
data
cap (a poem)compute
:
mod data {
pub fn fetch_data() -> Vec<u32> {
vec![1, 2, 3]
}
}
mod compute {
pub fn square_data(data: &Vec<u32>) -> Vec<u32> {
let mut new_data: Vec<u32> = vec![];
for i in data {
new_data.push((2));
}
new_data
}
}
use compute::square_data;
use data::fetch_data;
fn main() {
let data = fetch_data();
println!("fetch data: {:?}", data);
let new_data = square_data(&data);
println!("square data: {:?}", new_data);
}
Referencing modules in the same file is simple and straightforwarduse
module and the corresponding method is sufficient.
use compute::square_data;
use data::fetch_data;
The execution effect is as follows:
Maybe you have this question, since they are all in the same file, why do you need to define the module, direct definition of two functions is not enough.
this kind ofmain
It's available directly in the function, and saves you from having to reference the module.
This is because for functional functions that are relatively short in code but have high generality (e.g., simple string conversions, date format conversions), the
If you create a module file for each function, it's a bit cumbersome, but it's more refreshing to have it defined in a single file.
It's not too late to refactor this module into a separate file later if the functionality in the module becomes more and more complex.
2.2 Modules in the same directory
Module references in the same file are very simple, but as the code in the module grows, it is necessary to consider defining the module in a separate file.
For example, the module in the above code will bedata
cap (a poem)compute
The functions in the
The file structure is as follows:
The contents of the three files in the src directory are:
// Files
pub fn fetch_data() -> Vec<u32> {
vec![1, 2, 3]
}
// Documentation
pub fn square_data(data: &Vec<u32>) -> Vec<u32> {
let mut new_data: Vec<u32> = vec![];
for i in data {
new_data.push((2));
}
new_data
}
//
mod compute;
mod data;
use compute::square_data;
use data::fetch_data;
fn main() {
let data = fetch_data();
println!("fetch data: {:?}", data);
let new_data = square_data(&data);
println!("square data: {:?}", new_data);
}
Note the difference between here and the previous sectionModules in the same documentA few differences in the
First, in the module's documentationcap (a poem)
in which it is sufficient to define the function directly.
No need to addmod data{}
Such a module name, because the filenamedata
cap (a poem)compute
will automatically be used as the name of the module.
Secondly, inWhen a module from a different file is referenced in the file, the
The module needs to be defined first (equivalent to importing thecap (a poem)
),
mod compute;
mod data;
after thatuse
function therein.
use compute::square_data;
use data::fetch_data;
2.3 Modules in different directories
Finally, we're looking at how modules in different directories are referenced.
When the project features gradually increase and complexity, a functional module is more than one file, so it is necessary to encapsulate the module into a different directory.
For example, an ordinary Web system, in which the log module, database module and so on will be encapsulated into a separate directory.
Re-modify the file in the example and change the code to the following structure:
Encapsulates a logging module with 2 code files that output logs (cap (a poem)
), respectively, to output different levels of logs.
Then reference the logging module in your code and output the logs.
The code for each file is as follows:
// file
pub fn log_msg(msg: &str) {
println!("[DEBUG]: {}", msg);
}
// file
pub fn log_msg(msg: &str) {
println!("[INFO]: {}", msg);
}
// file
mod log;
use crate::log::debug;
use crate::log::info;
fn main() {
debug::log_msg("hello");
info::log_msg("hello");
}
Note that, in addition to these 3 files above, thelog
There's another one in the folder.Documentation.
This file is crucial, and its contents are the definition of thelog
What modules are in the folder.
// Documentation
pub mod debug;
pub mod info.
With this file.log folderonly thenRust
Think of it as a module before it can be used in theembedded in
mod log;
。
The execution effect is as follows:
Next, add anotherdatabase
module, allowing thedatabase
module referencelog
functions in the module.
That is, references between sibling modules.
The file directory structure changes to:
addeddatabase
The code in the two files in the module is as follows:
//
use crate::log::debug;
use crate::log::info;
pub fn create_db() {
debug::log_msg("start to create database");
info::log_msg("Create Database");
debug::log_msg("success to create database");
}
//
pub mod db;
The code in is as follows:
//
mod database;
mod log;
use crate::database::db;
fn main() {
db::create_db();
}
The results of the implementation are as follows:
Here's a point that needs to betake note of,database
modularThere is a direct reference to the
log
functions in the module.
It needs to be inMedium Definition
mod log;
。
even thoughcenter
No direct uselog
in the module still needs to be definedmod log;
if notdatabase
in the moduleunquotable
log
function in the module.
2.4. Relative and absolute references to modules
Rust modules can be referenced in two ways, relative path references and absolute path references.
The examples above are all absolute references, for example:use crate::log::debug;
,use crate::database::db;
。
absolute reference tocrate
To start with, you can put thecrate
The root module of the code understood as.
Relative references have two keywords, theself
cap (a poem)super
,self
indicates a module of the same level.super
Indicates a module at a higher level.
For example, in the above examplecap (a poem)
Modules in the file can also be changed to relative references:
//
// use crate::database::db;
use self::database::db;
//
// use crate::log::debug;
// use crate::log::info;
use super::super::log::debug;
use super::super::log::info;
3. Package (crate)
crate
beRust
The smallest unit of compilation and distribution, generally projects areSingle-crate multi-moduleThe.
If there are certain modules in the project that are highly generalized and may be released separately for use in other projects, then encapsulate these modules into thecrate
Not a bad idea either.
expense or outlayRust
Haven't been around long enough to actually use it muchcrate
case, see the ripgrep project if you're interested (/BurntSushi/ripgrep)。
4. Summary
This article focuses on theRust
Module (module
) are defined and referenced in a way that is intended to allow us to rationally organize and refactor our code when using Rust.