Location>code7788 >text

Code Organization for Rust Projects

Popularity:7 ℃/2024-08-04 12:33:27

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 onRustcommon way of organizing code in a language, I use theRustThe time is not long, so please correct me if I am wrong.

1. Module on management code

RustThree code management units are provided at the language level, namely:

  1. module: Modules, similar to namespaces in other languages, generally encapsulate the implementation of a specific function.

  2. crateRustThe smallest unit of compilation, eachcrateThere is afile to configure thecrateRelevant information.

     A `crate` can be a library or a binary.
    
  3. package: apackagecan 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 thecrateTurn it into apackageembodyMultiple crates + multiple modules
For example, the officially recommended project for learning Rust, ripgrep(/BurntSushi/ripgrep)。
just as muchcrateof the program, eachcratehave their ownConfiguration.

2. Modules

packagecratecap (a poem)moduleOf the three ways to organize code, the most common is themodule
The following is the main introductionRustcentermoduleThe 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

Rustmodules 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 indatacap (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 straightforwardusemodule 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 ofmainIt'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 bedatacap (a poem)computeThe 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 filenamedatacap (a poem)computewill 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, thelogThere's another one in the folder.Documentation.
This file is crucial, and its contents are the definition of thelogWhat modules are in the folder.

// Documentation
pub mod debug;
pub mod info.

With this file.log folderonly thenRustThink of it as a module before it can be used in theembedded inmod log;

The execution effect is as follows:

Next, add anotherdatabasemodule, allowing thedatabasemodule referencelogfunctions in the module.
That is, references between sibling modules.
The file directory structure changes to:

addeddatabaseThe 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 ofdatabasemodularThere is a direct reference to thelogfunctions in the module.
It needs to be inMedium Definitionmod log;
even thoughcenterNo direct uselogin the module still needs to be definedmod log;
if notdatabasein the moduleunquotablelogfunction 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 tocrateTo start with, you can put thecrateThe root module of the code understood as.

Relative references have two keywords, theselfcap (a poem)superselfindicates a module of the same level.superIndicates 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)

cratebeRustThe 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 thecrateNot a bad idea either.

expense or outlayRustHaven't been around long enough to actually use it muchcratecase, see the ripgrep project if you're interested (/BurntSushi/ripgrep)。

4. Summary

This article focuses on theRustModule (module) are defined and referenced in a way that is intended to allow us to rationally organize and refactor our code when using Rust.