模块系统相关:Workspace、Package、Crate、Module

厘清Workspace、Package、crate和module的关系

Package: 包含Cargo.toml

package就是cargo new的产物,里面包含一个cargo.toml,包名就写在里面的package里。比如substrate的一个包代码:

substrate/Cargo.toml at master · paritytech/substrate

[package]
name = "sc-allocator"
version = "4.1.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "Collection of allocator implementations."
documentation = "https://docs.rs/sc-allocator"
readme = "README.md"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
log = "0.4.17"
thiserror = "1.0.30"
sp-core = { version = "6.0.0", path = "../../primitives/core" }
sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" }
  1. package表明该package的基本信息
  2. dependencies表示该package依赖的其他package

workspace与package

  • workspace+members: 并发代表当前package包含的所有subpackage,只是指明一个工作区的所有package

A Cargo.toml file can simultaneously define a package and a workspace to which it belongs, but that package is still a member of that workspace, not the other way around.

具体对比package和crate

package和crate

在rust中,最小编译单元叫crate,package是一个或多个crate的集合。

也就是说,如果package没有指明crate,本身就是一个crate

在Cargo.toml的[bin]/[lib]中指明

再来对比workspace、package和crate

hierarchy

Workspace → Package → Crate

Generally, a package exposes only one crate. Most library crates don’t even have an associated binary crate(s) in their package. It’s due to this that package/crate terminology is often used interchangeably; for lib crates it is in the 90% case. Package is also a generic term that people not familiar with Rust’s ecosystem can understand, where crate is a Rust-specific piece of jargon.

The two concepts are still meaningfully different – while conventionally package and lib crate have the same name ( modulo hyphens vs underscores), this is not required in any way – but for the most part there isn’t an appreciable difference unless you’re paying attention to the weeds and edge cases.

module

在rust中,module(模块)更多还是一种逻辑上的概念,主要使用mod关键字,下面会具体说说

模块主要还是为了控制作用域(scope)和隐私(privacy)

#![allow(unused)]
fn main() {
mod say {
    pub fn hello() {
        println!("Hello, world!");
    }
}
}

整理说一下rust的模块系统

Rust模块系统两种视角程序猿文件结构rustc:module tree可执行rootsrc/main.rs-> binary crate(默认与cargo.toml->[package].name同名)库rootsrc/lib.rs-> lib crate(默认与cargo.toml->[package].name同名)crate<code>编译的最小基本单位</code>project的五个层级workspacepackagecratesmodulespathsbin文件夹:可以直接使用src/lib.rscrates.io保存的什么?发布流程cargo logincargo package$ cargo help package从帮助信息结合substrate源码实验🧪可知:1. 从当前目录开始执行路径开始,首先去父文件夹找Cargo.toml,然后找当前目录的Cargo.toml,找不到就报错2. 找到的Cargo.toml如果有workspace配置,就按照workspace里面的subpackage顺序来依次打包3. 每次打包的标志为src/main.rs或者src/lib.rs,且src同级存在Cargo.toml,Cargo.toml里面有[package]4. 开始打包为上传到crate.io的格式5. 依次打包6. 所有依赖必须是在crate.io可以找到的,找不到就报错7. 以包含Cargo.toml父文件夹为项目根目录,放在target/package里面cargo publishcargo yankcargo ownercrate.io包含代码总结1. 只包含最小crate内容,也就是src/main.rs或者src/lib.rs + Cargo.toml2. rust只能允许一级嵌套,使用workspace分出subpackage

联想对比

  1. golang的模块系统
  2. python/js的模块系统
  3. mdbook的所有文章只能挂到SUMMARY才能生成链接。

module tree

  • module Tree只有一个入口(根),src/main.rs或src/lib.rs
  • 默认情况下,lib.rs和main.rs的crate都和cargo.toml里面的[package].name同名
  • 但是cargo.toml里面可以给crate重命名:[lib]重命名lib.rs, [binary]重命名main.rs

模块使用方式

模块使用方式存在形式嵌套模块: mod可以在一个文件中定义多个模块文件模块:不带mod的文件,以文件名为模块名目录模块目录同级有同名文件同名文件中导入目录中的文件模块/嵌套模块在同名文件中开始使用隐私管理与导出导入pub: 导出-变为公有导入方式:use: 导入-进入当前作用域再次导入: pub use嵌套导入:使用{}作为嵌套两类导入路径crate: 绝对导入相对导入: 提高内聚self: 与当前模块相关super: 从父模块导入

关于细节说明

下方对应小标题会对思维导图中的重点内容进行详叙和代码展示

目录模块

tree -L 2                                                                                                      ─╯
.
├── foo
│   └── bar.rs
├── foo.rs
└── main.rs

1 directory, 3 files
  • foo/bar.rs

pub struct Bar; // 导出给foo.rs使用

impl Bar {
    pub fn hello() {
        println!("Hello from Bar !");
    }
}
  • foo.rs

mod bar; // 使用同名目录foo下的文件模块foo/bar.rs

pub use self::bar::Bar; // 再次指定导入bar::Bar

pub fn do_foo() {
    println!("Hi from foo!");
}

嵌套导入

// nested_imports.rs
// 使用{}嵌套导入
use std::sync::{Arc, Mutex, mpsc::{channel, Sender, Receiver}};

fn consume(_tx: Sender<()>, _rx: Receiver<()>) { } 

fn main() {
    let _ = Arc::new(Mutex::new(40));
    let (tx, rx) = channel();
    consume(tx, rx);
}

pub/crate导出

pub(crate) fn fn_name() {}

Rust 中元素的隐私性是从模块层面开始的。作为程序库的作者,要从模块向用户公开一些内容可以使用关键字 pub。

但是对于有一些元素,我们只想暴露给软件包中的其他模块,而不是用户。

在这种情况下,我们可以对元素使用 pub(crate)修饰符,这允许元素仅在软件包内部暴露

参考资源

不要把其他人的话直接照抄!

保持批判,有所取舍,知行合一, 方见真我

online-book

fragment

[workspace]
resolver = "2"

members = [
    "bin/node-template/node",
]
[profile.dev.package]
blake2 = { opt-level = 3 }

crate

A crate is the [lib] or [[bin]] tables in the Cargo.toml. At most one lib crate may be present, but an arbitrary number of bin crates may be present.

You won’t see these tables added explicitly too often, because they’re implicitly present if you have src/lib.rs (lib crate) and/or src/main.rs (bin crate).

# Example of customizing the library in Cargo.toml.
[lib]
crate-type = ["cdylib"]
bench = false
# Example of customizing binaries in Cargo.toml.
[[bin]]
name = "cool-tool"
test = false
bench = false

[[bin]]
name = "frobnicator"
required-features = ["frobnicate"]

《The Rust Programming Language》相关整理

  1. 一个包是一个或多个提供一组功能的 crates。一个package包含一个 Cargo.toml 文件,该文件描述了如何构建这些crate。
  2. crate 可以是二进制 crate 或库 crate。
  • 二进制 crate 是可以编译成可执行文件的程序,可以运行,例如命令行程序或服务器。

它们必须有一个名为 main 的函数,该函数定义了可执行文件运行时会发生什么。到目前为止,我们创建的所有 crate 都是二进制 crate。

  1. 库 crates 没有 main 函数,它们不会编译为可执行文件。它们定义了旨在与多个项目共享的功能。

例如,我们在第 2 章中使用的 rand crate 提供了生成随机数的功能。

  1. 下面是一些规则:
  • 一个包最多可以包含一个库 crate。它可以包含任意数量的二进制 crate,但它必须至少包含一个 crate(库或二进制)。
  • 当我们输入cargo new时,Cargo 创建了一个 Cargo.toml 文件,cargo将会给我们一个package。
  • 查看 Cargo.toml 的内容,没有提到 src/main.rs,因为 Cargo 遵循一个约定,即 src/main.rs 是与包同名的二进制 crate 的 crate 根。
  • 同样,Cargo 知道如果包目录包含 src/lib.rs,则该包包含一个与包同名的库 crate,并且 src/lib.rs 是它的 crate 根。

Cargo 将 crate 根文件传递给 rustc 以构建库或二进制文件。

  • 在这里,我们有一个只包含 src/main.rs 的包,这意味着它只包含一个名为 my-project 的二进制 crate。
  • 如果一个包包含 src/main.rs 和 src/lib.rs,它有两个 crate:一个二进制文件和一个库,两者都与包同名。

通过将文件放在 src/bin 目录中,一个包可以有多个二进制 crate:每个文件都是一个单独的二进制crate

Module System

一个问题几乎总会由许多小问题组成。module system是为了定义清楚各个小问题的边界。这样更容易和更方便的管理问题。而大问题的解法,就是把小问题的解法组合起来。

project,package, crate, module这些概念感觉相似。实际上,一个package/project可以包含多个 binary crates和一个或者零个library binary。一个crate可以包含多个module。可以认为package就是一个project,一个crate就是一个暴露给外界的逻辑单元,一个module就是一个小问题的解法。

当project里面有lib.rs说明这个project是一个library crate,这个library的名字是project的名字。main.rs/main2.rs都可以直接使用这个library crate。我们可以认为bin文件夹里面是单独的crate,它们默认导入了这个library crate。

一个crate就是一个暴露给外界的逻辑单元,一个module就是一个小问题的解法

关于找不到模块: 这就是module tree的体现,be explicit。所有模块都需要添加到crate root(src/main.rs或者src/lib.rs)里面。也就是要显示地指明module tree的结构。这也就是我们经常在main.rs/lib.rs里面看到许多mod xxx的原因。比如这里的代码

它们的存在就是为了将project里面的modules 加到这个crate里面。比如在main.rs 里面看到mod channel,就是将module channel加进crate的module tree来。

相关引用: Mentally Modelling Modules - In Pursuit of Laziness

The other way of learning to use car indicators is much simpler: push the stalk in the direction that you would turn the wheel. Of course this still leaves the possibility that some people will push the wrong stalk, and briefly activate their windshield wipers . But it’s a much easier mistake to notice, and the consequences are minor.

Back to Rust’s modules. I keep seeing articles trying to offer a simple or clear explanation of how they work, that end up unnecessarily complicated in a way that feels a lot like the “up/down” model of car indicators. The explanation that makes the basics of Rust modules clear to me is this:

  • Child modules must always be declared in the parent module, or they don’t exist.

  • The content of child modules may be defined either inline in the parent as mod child { ... }, or in a file with a relative path of ‘./child.rs’ or ‘./child/mod.rs’.

Did I miss anything important?

Without this basic explanation up-front, I have no idea what to do with the stream of information I’m reading in a lengthy article on the topic – I’ve been given no scaffolding onto which to bolt all the details and examples. So this is the “bottom line” that I would like to see “up front” in descriptions of Rust’s modules.

Other misunderstandings, e.g., around item visibility, are explained really well by the compiler if you get them mixed up, so I’m not sure how much value there is in mixing them in to articles about how modules are structured before those two basic facts are presented.

The processing of that source file may result in other source files being loaded as modules. It is not that one source file makes up a crate: it’s that starting from that one source file, you can find all the files making up the crate, as opposed to other compilation models where the compiler might be given many file names to start from.

其实从代码完整性考虑,crate确实就是编译的最小基本单位。因为它不仅指一个源码文件(xx.rs),而是包含这个源码文件里面引入的所有其他module。这个时候,rustc才会开始编译这个crate

The exact things hosted on crates.io are crates inside packages. A crate is the output artifact of the compiler. The compilation model centers on artifacts called crates. Each compilation processes a single crate in source form, and if successful, produces a single crate in binary form: either an executable or some sort of library. A package is an artifact managed by Cargo, the Rust package manager.

local