《RUST权威指南》第7章笔记

6 阅读7分钟

注:本篇文章全是由我本人通过学习所感悟的内容,所以不能保证是绝对正确的,如果有发现我文章的错误,麻烦请指出我的错误让我能明白我自己错在了哪里,希望大家一起进步。

使用包、单元包和模块管理日渐复杂的项目

在我们开发项目的时候,总会因为代码越写越多导致可读性逐渐变差,而在RUST中我们可以通过模块来管理我们的项目,将我们项目拆分为模块分开存放。

一、 包、Crate和模块

首先我们得先了解rust中一些基础的概念:包、Crate和模块
包:当我们每次创建项目,不管是一个二进制项目还是一个库项目,我们都将这整个项目称为一个包。

Crate:在RUST中,我们有一个Crate root的概念,crate root就是我们创建好项目后src/main.rs 或 lib.rs也可以是src/bin/下的所有rs文件,需要注意的是一个包中至少包含一种crate root但是lib.rs只能最多1个,以crate root入口文件所展开的一套模块文件就是一个crate,后面会举例。

模块:模块就是我们具体的拆分的功能代码了,模块可以以内联或外部存放两种方式。

二、 如何拆分功能代码来管理项目

上面简单介绍了一下基础概念不懂也没关系,我们直接拿出具体案例。
首先我们来拿出一段代码

fn print_a() {
    println!("print a!!!");
}

fn print_b() {
    println!("print b!!!");
}

这里我们定义两个函数,分别输出了一段内容,我们假设这是两个功能性代码,就像一个是a的功能性代码,一个是b的功能性代码,我们想把它拆分开存放,存放在两个独立文件,或者按照逻辑拆分成两个模块。

  1. 内联方式拆分为两个模块
mod a {
    fn print_a() {
        println!("print a!!!");
    }
}

mod b {
    fn print_b() {
        println!("print b!!!");
    }
}

  1. 外部存放的方式 项目/src/lib.rs
mod a;
mod b;

项目/src/a.rs 或者 /src/a/mod.rs

fn print_a() {
    println!("print a!!!");
}

项目/src/b.rs 或者 /src/b/mod.rs

fn print_b() {
    println!("print b!!!");
}

前面我们说了,模块可以内联,上面就是一个内联与外部存放的例子,1. 我们通过了mod 模块名{}的方式,声明了两个模块a与b,并将功能函数存放在其中。2. 我们通过在lib.rs中先定义了mod,但是我们把它拆分到了两个新的rs中,当你定义了一个外部mod时,以a举例,他会寻找/src/a/mod.rs这个文件,但是2018RUST新版本会直接找/src/a.rs,所以建议使用扁平化的方式去拆分文件,上面我们演示了如何拆分一个代码分开存储,后面我们来讲如何去使用这些拆分了的代码。

三、 如何使用模块

在使用模块之前,我们需要先了解两个新的知识:路径 公有及私有。

  1. 路径
    在windows上找一个文件时我们都会有一个路径比如C:\system,表示的就是C盘下的system文件夹,在开始的时候我们提了一个crate root的概念,在这里我们就会用到了,在项目名/src/lib.rs中我们定义了a,b两种模块,而lib.rs就是我们的crate root也就是根,就和我们C:是类似的,有点像在C盘中创建了a、b两个文件夹一样,和windows一样,我们在实际寻找模块也可以通过两种方式去找,一种是绝对路径,一种是相对路径,以下是两个例子

1.1 绝对路径与相对路径
像前面举例一样,我们假设c盘中有a、b两个文件夹,而文件夹中有两个文件也就是print_a和print_b,在windows中我们会像这样去寻找 C:\a\print_a 这就是一个绝对路径而在rust中是这样展示的C::a::print_a。

上面这个是绝对路径而相对路径是这样的假设我们现在在a文件夹我们要找下面的print_a就是这样表示 ..\print_a,而RUST中是这样的a::print_a,这是RUST中的相对路径。

但是我们有一个关键字,super,这个其实指的就是父模块,什么意思呢?是这样的,比如我现在在a模块中编写一个功能但是需要用b模块中的print_b功能,那么我就可以使用super::b::print_b,因为a模块的父级就是C:而super此时代表的就是父级,然后去找父级下的b中的print_b。

1.2 公有及私有 前面我们介绍了路径,但是在实际编写代码的时候我们会发现问题,我们使用了路径去调用函数但是会编译失败并提示是私有的这是什么呢?在RUST中,我们的模块使用mod 定义的时候默认情况下里面的内容是私有的,也就是父级无法访问子模块内部的私有项,而子模块可以知道父级的存在,假设我们想要将某些功能让别人能够调用,就需要使用pub mod来进行公有定义,这样上级就能知道模块的存在,但是需要注意的事这里有一条件,路径中每一个都必须是公有的。举个例子,我们使用了这个项目的a模块的print_a函数,并且每一个都是公有的,但是有一些列外比如struct结构体,在结构体中你仅仅设置结构体为公有但它的属性的数据任然是私有的,更多其他的数据需要查看以下官方文档。 src/main.rs

pub mod a {
    pub fn print_a() {
        println!("print a!!!");
    }
}

fn main() {
    crate::a::print_a();
}

1.3 use use可以将模块引入到当前的作用域,引入到作用域后可以直接调用print_a函数。

pub mod a {
    pub fn print_a() {
        println!("print a!!!");
    }
}
mod b {
    fn print_b() {
        println!("print b!!!");
    }
}
use crate::a::print_a;
fn main() {
    print_a();
}

1.4 pub use重导出 接下来我们介绍一个用法叫重导入,他有什么作用呢,可以用于重构,对外隐藏内部模块结构,它可以把模块引入当前作用域同时可以被外部代码引入作用域。以下是一个实例,我们通过重导出将print_a直接导入到了当前作用域,所以下面的main中我们可以直接使用,同时当被挂在到了当前路径,其他使用者使用包时可以直接使用包名::print_a来使用,与普通 use 仅作用于当前作用域不同,pub use 会将条目暴露给外部调用者,从而改变包的对外 API 路径。

pub mod a {
    pub fn print_a() {
        println!("print a!!!");
    }
}
pub use a::print_a;
mod b {
    fn print_b() {
        println!("print b!!!");
    }
}

fn main() {
    print_a();
}

1.5 use as use as可以在导入模块到作用域的情况下修改他的名字,以下代码不仅将a模块下的print_a引入了作用域同时修改名字为printa。

pub mod a {
    pub fn print_a() {
        println!("print a!!!");
    }
}
mod b {
    fn print_b() {
        println!("print b!!!");
    }
}
use crate::a::print_a as printa;
fn main() {
    printa();
}

1.6 使用外部包 在我们平常编写代码,需要使用别人写好的功能,就需要使用别人的包中模块,而如何导入别人的包,在项目下的cargo.toml中的[dependencies]下导入包名及版本就好,下面实例导入了一个rand包,且版本号为0.8.5 restaurant/cargo.toml

[package]
name = "restaurant"
version = "0.1.0"
edition = "2024"


[dependencies]
rand = "0.8.5"

本篇文章到这里就结束了,这是我自己学习完本章后自己的理解总结,可能不全或有漏的地方,可以查看官方文档。