当前位置: 首页 > news >正文

Rust编程学习 - mod (模块)是用于在crate 内部继续进行分层和封装的机制

下面还是用一个完整的示例演示一下build.rs功能如何使用。假设我们现在要把当前项 目最新的commit id记录到可执行程序里面。这种需求就必须使用自动代码生成来完成。首 先新建一个项目with_commit_hash:

cargo new -bin with_commit_hash

然 后 , 到Cargo.toml 里 面 加 上 :

build="build.rs"

当然,对应的,要在项目文件夹下创建 一 个build.rs 的 文 件 。我们希望能在编译过程中生成一份源代码文件,里面记录了一个常量,类似这样:

const CURRENT_COMMIT_ID:&static str ="123456789ABCDEF";

查找当前git 的最新commit id 可以通过命令git rev-parse HEAD 来完成。所以, 我 们 的build.rs 可以这样实现:

use std: :env;
use std: :fs: :File;
use std: :io: :Write;
use std: :path: :Path;
use std: :process: :Command;
fn main() {let out_dir = env: :var ("OUT_DIR").unwrap();let dest_path = Path: :new( & out_dir)·join("commit_id.rs");let mut f = File: :create( & dest_path).unwrap();let commit = Command: :new("git").arg("rev-parse").arg("HEAD").output().expect("Failed to execute git command");let commit = String: :from_utf8(commit.stdout).expect("Invalid utf8 string");let output = format ! (r#"pub const CURRENT_COMMIT_ID:&'static str =" {}";"#, commit);f.write_all(output.as_bytes()).unwrap();
}

输出路径是通过读取OUT_DIR环境变量获得的。利用标准库里面的Command类型, 我们可以调用外部的进程,并获得它的标准输出结果。最后再构造出我们想要的源码字符 串,写入到目标文件中。

生成了这份代码之后,我们怎么使用呢?在main.rs 里面,可以通过宏直接把这部分源 码包含到项目中来:

include ! (concat ! (env ! ("OUT_DIR"), "/commit_id.rs"));
fn main() {println ! ("Current commit id is:{}", CURRENT_COMMIT_ID);
}

这个 include! 宏可以直接把目标文件中的内容在编译阶段复制到当前位置。这样 main 函数就可以访问CURRENT_COMMIT_ID 这个常量了。大家要记得在当前项目使用git 命令新建几个commit。然后编译,执行,可见在可执行程序中包含最新commit id这个任务 就完全自动化起来了。


模块管理

前面我们讲解了如何使用cargo 工具管理crate 。 接下来还要讲解 一 个crate 内部如何管 理模块。可惜的是, Rust 设计组觉得目前的模块系统还有一些瑕疵,准备继续改进,在编写 本书的时候这部分内容正处在热火朝天的讨论过程中。改进的目标是思维模型更简洁、更加 具备一致性、方便各个层次的用户。所以本书在这部分不会强调太多的细节,因为目前一些 看起来比较繁复的细节将来很可能会得到简化。


文件组织

mod (模块)是用于在crate 内部继续进行分层和封装的机制。模块内部又可以包含模 块。Rust 中的模块是一个典型的树形结构。每个crate 会自动产生一个跟当前crate 同 名 的模块,作为这个树形结构的根节点。比如在前面使用cargo 创建多个项目的示例中,项 目 hello_world依赖于项目good_bye, 我们要调用good_bye中的函数,需要写good_ bye::say();, 这是因为say 方法存在于good_bye 这个mod 中。它们组成的树形关系
如下图所示:

在一个crate 内部创建新模块的方式有下面几种。

  • 一个文件中创建内嵌模块。直接使用mod 关键字即可,模块内容包含到大括号 内 部 。
    mod name{fn items(){} …}
  • 独立的一个文件就是一个模块。文件名即是模块名。
  • 一个文件夹也可以创建一个模块。文件夹内部要有一个mod.rs 文件,这个文件就是这 个模块的入口。

使用哪种方式编写模块取决于当时的场景。如果我们需要创建一个小型子模块,比如单 元测试模块,那么直接写到一个文件内部就非常简单而且直观;如果一个模块内容相对有点 多,那么把它单独写到一个文件内是更容易维护的;如果一个模块的内容太多了,那么把它 放到一个文件夹中就更合理,因为我们可以把真正的内容继续分散到更小的子模块中,而在 mod.rs 中直接重新导出(re-export) 。 这 样mod.rs 的源码就大幅简化,不影响外部的调用者。

可以这样理解:模块是一种更抽象的概念,文件是承载这个概念的实体。但是模块和文 件并不是简单的一一对应关系,用户可以自己维护这个映射关系。

比如,我们有一个crate 内部包含了两个模块, 一个是caller 一个是worker 。我们可以有 几种方案来实现。
方案一:直接把所有代码都写到 lib.rs里面:

//<lib.rs>
mod caller {fn call() {}
}
mod worker {fn work1() {}fn work2() {}fn work3() {}
}

方案二:把这两个模块分到两个不同的文件中,分别叫作caller.rs和 worker.rs 。那么我们的项目就有了三个文件,它们的内容分别是:

//<lib.rs>
mod caller;
mod worker;
//<caller.rs>
fn call() {}
//<worker.rs>
fn work1() {}
fn work2() {}
fn work3() {}

因为lib.rs是这个crate的入口,我们需要在这里声明它的所有子模块,否则caller.rs和 worker.rs 都不会被当成这个项目的源码编译。

方案三:如果worker.rs 这个文件包含的内容太多,我们还可以继续分成几个文件:

/1<lib.rs>
mod     caller;
mod   worker;
/ / <caller.rs > fn call() {}
//<worker/mod.rs>
mod worker1;
mod worker2;
mod worker3;
//<worker/worker1.rs>
fn work1() {}
//<worker/worker2.rs>
fn work2() {}
//<worker/worker3.rs>
fn work3() {}

这样就把一个模块继续分成了几个小模块。而且worker 模块的拆分其实是不影响 caller 模块的,只要我们在worker 模块中把它子模块内部的东西重新导出(re-export) 就可以了。这 个是可见性控制的内容,下面我们继续介绍可见性控制。


可见性

我们可以给模块内部的元素指定可见性。默认都是私有,除了两种例外情况: 一是用 pub 修饰的trait 内部的关联元素(associated item), 默认是公开的;二是pub enum 内部的成 员默认是公开的。公开和私有的访问权限是这样规定的:

  • 如果一个元素是私有的,那么只有本模块内的元素以及它的子模块可以访问;
  • 如果一个元素是公开的,那么上一层的模块就有权访问它。
    示例如下:
mod top_mod1 {pub fn method1() {}pub mod inner_mod1 {pub fn method2() {}fn method3() {}}mod inner_mod2 {fn method4() {}mod inner_mod3 {fn call_fn_inside() {super: :method4();}}}}
fn call_fn_outside() {: :top_mod1: :method1();: :top_mod1: :inner_mod1: :method2();
}

在这个示例中,top_mod1 外部的函数call_fn_outside(),有权访问method1(),因为它是用pub 修饰的。同样也可以访问method2(), 因为inner_mod1 是pub 的,而且 method2也是pub 的。而inner_mod2 不是pub 的,所以外部的函数是没法访问method4 的。但是call_fn_inside是有权访问 method4的,因为它在method4 所处模块的子模块中。

模块内的元素可以使用pub use 重新导出 (re-export) 。 这也是Rust 模块系统的一个重 要特点。示例如下:

mod top_mod1 {pub use self: :inner_mod1: :method1;mod inner_mod1 {pub use self: :inner_mod2: :method1;mod inner_mod2 {pub fn method1() {}}}}
fn call_fn_outside() {: :top_mod1: :method¹ ();
}

在call_fn_outside 函数中,我们调用了 top_mod1 中的函数method1。 可是我 们注意到,method1 其实不是在top_mod1内部实现的,它只是把它内部inner_mod1 里面 的函数重新导出了而已。pub use 就是起这样的作用,可以把元素当成模块的直接成员公开 出去。我们继续往下看还可以发现,这个函数在inner_mod1里面也只是重新导出的,它 的真正实现是在 inner_mod2里面。

这个机制可以让我们轻松做到接口和实现的分离。我们可以先设计好一个模块的对外 API, 这个固定下来之后,它的具体实现是可以随便改,不影响外部用户的。我们可以把具 体实现写到任何一个子模块中,然后在当前模块重新导出即可。对外部用户来说,这没什么 区别。

http://www.dtcms.com/a/585541.html

相关文章:

  • 物联网中台搭建以及规则定义
  • 怎么做自己优惠券网站做网站聊城
  • 实时大数据计算中,windowDuration,slideDuration,trigger,watermark的关系
  • 网站建设开场介绍话术常德投诉网站
  • FetchAPI 请求流式数据 基本用法
  • C#知识补充(二)——命名空间、泛型、委托和事件
  • 就业|高校就业|基于ssm+vue的高校就业信息系统的设计与实现(源码+数据库+文档)
  • 县网站建设网页设计与制作教案模板
  • 无线通信是如何实现的
  • 【开题答辩实录分享】以《植物爱好者交流平台的设计与实现》为例进行答辩实录分享
  • 【打靶日记】HackMyVM 之 Aria
  • 零基础学AI大模型之LangChain Embedding框架全解析
  • 使用Procise打包和烧写BOOT.bin文件
  • 哪个网站做课件能赚钱网站建设的费用是不是含税的
  • 大朗仿做网站深圳做网站推广哪家好
  • 电力分配的艺术:从城市供电到二分查找的奇妙旅程
  • CentOS7 使用RDO部署单节点Train版OpenStack
  • Verilog运算符
  • Redis入门 - 基本概念和九种数据类型
  • mc数学库
  • CodeBuddy接入GLM4.6:新一代AI编程助手的能力革命与性能突破
  • 网站备案个人好还是企业好wordpress新文章数据库
  • 用html5写一个打巴掌大赛
  • 基于python大数据的高考志愿推荐系统
  • Web APIs 学习第五天:日期对象与DOM节点
  • windows 根据端口号关闭进程脚本
  • 推荐电商网站建设微信小程序商城制作一个需要多少钱
  • 【Web3】web3概念术语
  • 自己做的网站403企业咨询合同
  • 深海智脑:全球首个深海生境智能多模态大模型的技术突破与产业展望