大型 Rust 项目经验分享| Databend 与您共同进步

本文探讨了如何组织大型Rust项目,推荐扁平化的结构以提高一致性、可读性和扩展性。作者介绍了rust-analyzer项目的案例,并分享了关于工作空间管理、命名策略和自动化工具的小技巧。

# 大型 Rust 项目工作空间

> 原文:[https://matklad.github.io/2021/08/22/large-rust-workspaces.html](https://matklad.github.io/2021/08/22/large-rust-workspaces.html) 

在本篇文章中,我将分享我组织大型 Rust 项目的经验。但这绝不是权威的,只是我通过尝试和错误中发现的一些小技巧。

Cargo,作为 Rust 的构建系统,遵循约定大于配置的原则。它不仅为小型项目提供了一套良好的默认配置集,尤其为公共 crates.io 库量身定做。虽然这些默认值并不完美,但它们已经足够用了。这对整个生态系统的一致性也是值得欢迎的。

然而当涉及到大型的、多 crate 的项目时,Cargo 就不那么统一了,它被组织成一个 Cargo 工作空间。而工作空间是灵活的 —— Cargo 对工作空间的布局并没有一个偏好统一。因此,人们会尝试不同的东西,取得不同程度的效果。

回到标题,我认为对于代码行数在一万到一百万之间的项目,扁平化结构是最为合理的。此处 [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer) (多达 200k 行)是一个比较好的例子,它的项目组织如下:

```shell
rust-analyzer/
  Cargo.toml
  Cargo.lock
  crates/
    rust-analyzer/
    hir/
    hir_def/
    hir_ty/
    ...
```

在 repo 的根部,Cargo.toml 定义了一个虚拟清单:

*Cargo.toml:*

```toml
[workspace]
members = ["crates/*"]
```

其他的东西(包括 rust-analyzer "main" crate)都嵌套在 `crates/` 下的某一个层级中。每个目录的名称都等于 `crate` 的名称。

*crates/hir_def/Cargo.toml:*

```toml
[package]
name = "hir_def"
version = "0.0.0"
edition = "2018"
```

在撰写本文时,`crates/` 中有 32 个不同子文件夹。

## 扁平式比嵌套式更好

有趣的是,这个建议和按层级组织的习惯倾向(注:按照我们平时开发习惯来说)刚好对立:

```shell
rust-analyzer/
  Cargo.toml
  src/
  hir/
    Cargo.toml
    src/
    def/
    ty/
```

在这种情况下,有几个原因可以说明树形结构是低级的:

1. crates 的 cargo 分级命名空间是扁平的。在 `Cargo.toml` 中不可能写出 `hir::def` ,所以一般 crate 的名字中都有前缀。树状排列创造了另一种层次结构,这就增加了不一致的可能性。
2. 即使是比较大的列表也比小的树更容易让人一目了然。`ls ./crates` 给出了项目的层级概览,而且这看起来足够小。

```shell
> ls ./crates

base_db
cfg
flycheck
hir
hir_def
hir_expand
hir_ty
ide
ide_assists
ide_completion
ide_db
ide_diagnostics
ide_ssr
limit
mbe
parser
paths
proc_macro_api
proc_macro_srv
proc_macro_test
profile
project_model
rust-analyzer
sourcegen
stdx
syntax
test_utils
text_edit
toolchain
tt
vfs
```

在基于树状结构的布局做同样的事情比较困难的:

- 只从单层级上看并不能告诉你哪些文件夹包含嵌套的 crate

- 而在所有层级上看又会列出太多的文件夹(无关文件干扰视觉)

**正确方式**:***只看包含 Cargo.toml 的文件夹可以得到正确的结果,但并没有 `ls` 那样简单***。

嵌套结构确实比扁平结构更容易扩展。但常数很重要 —— 在你达到一百万行代码之前,项目中的 crates 数量可能会充满一个屏幕。

3. 层级布局的最后一个问题是:***没有完美的分层结构***。但是对于扁平结构,增加或拆分 crates 的代价微不足道。在树状结构下,你需要弄清楚把新的 crate 放在哪里,而且,如果已经没有一个完美的匹配,你将不得不选择以下几种情况:

- 在顶部附近添加一个愚蠢的空的文件夹
- 添加到一个巨无霸的 utils 文件夹
- 将代码放在一个已存在但是不是很理想的目录中(*所以结构会随着维护而慢慢恶化*)

对于长期维护的多人项目来说,这是一个重要的问题 —— 树状结构往往会随着时间的推移而恶化,而扁平结构则不怎么需要维护。

## 小技巧

> **让工作空间的根部成为虚拟清单。**

这可以驱使我们把 main crate 放在根目录下,但这样做会使根目录被 `src/` 污染,需要在每个 Cargo 命令中传递 `--workspace`,并向其他一致的结构添加异常。

> **反对从文件夹名称中去除普通前缀。**

如果每个板块的名字都和它所在的文件夹一模一样,这让导航和重命名就会变得更容易。反向依赖的 Cargo.toml 同时提到了文件夹和 crate 的名称,当它们完全相同的时候就很有用。

> **对于大型项目来说,很多版本库的臃肿往往来自于自动化。**

Makefiles 和各种 prepare.sh 脚本。为了避免臃肿和临时工作流程的泛滥,可以将所有的 Rust 自动化写在一个专门的 crate 里。这里安利一个有用的库:[cargo-xtask](https://github.com/matklad/cargo-xtask)。

> **对于你不打算发布的内部 crate,可以使用 `version = "0.0.0"`。**

如果你确实想发布具有符合语义版本 API 的 crate 的子集,那么要非常慎重对待它们。将所有这样的crate提取到一个单独的顶层文件夹,即 `libs/`,这样做对未来可能是有意义的。这使得检查 `libs/` 中的东西是否使用了 `crates/` 中的东西更加容易。

> **由一个文件组成一个 crate。**

对于这些文件,我们很容易陷入:把 `src` 目录展开,把 lib.rs 和 Cargo.toml 放在同一个目录下。但是我建议不要这样做 —— 即使 crate 现在是单文件,以后也可能会被扩展。
 

### 如何在 Windows 系统中安装 PythonQt Designer 并配置环境 #### 安装 PyQt6 和 Qt Designer 为了在 Windows 上使用 Qt Designer,可以通过 `pip` 命令安装最新的 PyQt6 模块。PyQt6 是一个用于创建图形用户界面 (GUI) 应用程序的工具包,并附带了 Qt Designer 工具。 运行以下命令可以完成 PyQt6 及其相关组件的安装: ```bash pip install pyqt6-tools ``` 此命令会自动下载并安装必要的依赖项,其中包括 Qt Designer[^1]。 #### 配置路径以便访问 Qt Designer 安装完成后,通常可以在以下目录找到 Qt Designer 文件(具体位置取决于 Python 解释器的位置): - **对于标准安装**:`C:\Users\<用户名>\AppData\Local\Programs\Python\<版本号>\Lib\site-packages\pyqt6_tools` - 或者通过脚本启动:`python -m pyqt6_designer`. 如果希望直接从文件资源管理器或桌面快捷方式打开 Qt Designer,则需将其可执行文件所在路径添加到系统的环境变量 PATH 中。操作方法如下: 1. 打开控制面板 -> 系统和安全 -> 系统 -> 高级系统设置。 2. 单击“环境变量”,在“系统变量”部分找到名为 “Path”的条目并编辑它。 3. 添加上述提到的设计工具所在的完整路径至列表末尾。 这样处理之后,在任意 CMD 终端窗口输入 designer.exe 就能调用该应用程序。 #### 测试安装成功与否 验证是否正确设置了所有内容的一个简单办法就是尝试加载设计模式本身或者利用 PyQT 创建一个小项目来看看能否正常渲染 UI 元素。下面给出一段简单的例子展示如何载入由设计师保存下来的 .ui 文件并通过 python 运行起来: ```python from PyQt6 import uic import sys from PyQt6.QtWidgets import QApplication, QMainWindow class MyUI(QMainWindow): def __init__(self): super(MyUI,self).__init__() # 加载 ui 文件 uic.loadUi('your_ui_file.ui', self) if __name__ == '__main__': app = QApplication(sys.argv) window = MyUI() window.show() try: sys.exit(app.exec()) except SystemExit: pass ``` 以上代码片段假设存在一个叫做 'your_ui_file.ui' 的文件位于当前工作目录下,它是之前通过 Qt Designer 构建出来的界面布局定义文档。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值