包与模块
随着你的程序从单文件脚本成长为完整的应用程序,你需要一种将代码组织到多个文件和目录中的方法。你还需要一种管理依赖的方式——引入他人编写的库,并跟踪你使用的版本。
Auto 通过 automan(其包管理器和构建工具)结合模块系统提供了一套完整的项目管理方案。模块系统让你控制可见性,并将代码组织成干净、可复用的单元。
在本章中,我们将讨论:
- 使用
automan管理项目 —— 包管理器和构建系统 auto.toml—— 项目清单和依赖声明- 定义模块 ——
mod关键字和嵌套模块 - 控制可见性 ——
pub关键字和默认私有 - 组织到文件 —— 多文件项目和
use关键字
使用 automan 管理项目
automan 是 Auto 的包管理器和构建工具,等价于 Rust 的 Cargo 或 Node.js 的 npm。它负责创建项目、构建代码、运行测试和管理依赖——全部通过一个命令行工具完成。
创建新项目
要创建一个新的 Auto 项目,运行 automan new:
$ automan new my_project
Created binary (application) `my_project` package
$ cd my_project这将生成一个具有以下结构的项目:
项目目录包含两个关键项:
auto.toml—— 项目清单文件(我们接下来详细讨论)src/main.auto—— 应用程序的入口点,包含main函数
你可以使用 automan run 构建并运行项目,它会编译所有 .auto 文件并执行生成的二进制文件。
automan 常用命令
| 命令 | 描述 |
|---|---|
automan new <name> | 创建新项目 |
automan build | 编译项目 |
automan run | 构建并运行 |
automan test | 运行测试 |
automan add <dep> | 添加依赖 |
auto.toml
auto.toml 文件是项目清单。它位于项目根目录,描述包的元数据和依赖。它使用 TOML 格式,与 Rust 的 Cargo.toml 相同。
包元数据
每个 auto.toml 以 [package] 部分开始:
[package]
name = "my_project"
version = "0.1.0"这告诉 automan 包的名称和版本。名称必须是有效的标识符——使用下划线而非连字符。
声明依赖
要使用外部库,将它们添加到 [dependencies] 部分:
当你运行 automan build 时,它会读取 auto.toml,从包注册中心下载指定的依赖,并将它们一起编译。解析出的确切版本记录在 auto.lock 中,确保可重现的构建。
Auto 与 Rust 工具链对比
概念 Auto Rust 包管理器 automancargo配置文件 auto.tomlCargo.toml锁文件 auto.lockCargo.lock编译器 autocrustc源文件 .auto.rs
定义模块
模块 是一个命名空间,将相关的函数、类型和常量组织在一起。在 Auto 中,你使用 mod 关键字后跟一个代码块来定义模块:
输出:
Server started on port 8080
Connecting to localhostmod 关键字创建一个命名空间。在 mod server { ... } 中,函数 start 被组织在 server 命名空间下。你通过点号访问它:server.start()。
模块可以嵌套。你可以在一个 mod 块内放置另一个 mod 块来创建层次结构:
mod network {
mod tcp {
fn listen(port int) {
print("TCP listening on port " + port)
}
}
}
fn main() {
network.tcp.listen(3000)
}这在各语言中的映射:
- Rust 使用
mod块配合::路径分隔符。模块内的函数需要标记pub才能从外部访问。 - Python 将模块展平为带前缀的函数名(
server_start、client_connect)。 - C 使用带前缀的函数名,与 Python 类似。
- TypeScript 使用驼峰式前缀名(
serverStart、clientConnect)。
控制可见性
Auto 遵循 Rust 的设计哲学:所有内容_默认私有_。模块内的项只在该模块内可见,除非你用 pub 显式标记为公开。
输出:
public helperpub 关键字使 helper 可以从 utils 模块外部访问。internal 函数没有 pub,所以它是私有的——只有 utils 内部的代码才能调用它。尝试从 main 调用 utils.internal() 会导致编译错误。
这是借鉴自 Rust 的刻意设计选择。默认私有意味着:
- 你必须显式决定暴露什么。公开的 API 表面总是有意为之。
- 内部实现细节保持隐藏。你可以重构模块的内部而不破坏使用它的代码。
- 编译器在编译时捕获对私有项的意外访问。
可见性在各语言中的映射:
- Rust 使用相同的
pub关键字和相同的默认私有语义。 - Python 没有原生的可见性控制——
_前缀约定表示"内部"但不强制执行。转译器将调用注释掉。 - C 使用
static实现文件级私有函数。 - TypeScript 没有模块级可见性;转译器添加
// private注释并注释掉调用。
组织到文件
随着项目增长,你会想将模块拆分到单独的文件中。Auto 遵循与 Rust 类似的约定:每个文件是一个模块,目录创建嵌套的模块命名空间。
项目结构
一个典型的多文件 Auto 项目如下:
my_project/
├── auto.toml
└── src/
├── main.auto
├── utils.auto
└── network/
├── mod.auto
├── server.auto
└── client.auto每个 .auto 文件成为一个模块。目录 network/ 成为 network 模块命名空间,其中 mod.auto 是其入口点。
use 关键字
use 关键字将模块引入作用域,使你可以直接引用其中的项。这类似于 Rust 的 use 或 Python 的 import:
输出:
public helper
Server started on port 8080use utils 将 utils 模块引入作用域。use network.server 将 network 目录中的 server 子模块引入作用域。导入后,你可以直接调用 utils.helper() 和 server.start()。
注意两种 use 形式的区别:
use utils—— 导入模块作为命名空间。你仍然通过它访问项:utils.helper()。use network.server—— 导入特定的子模块。你可以直接调用其函数:server.start()。
use 在各语言中的映射:
- Rust 为基于文件的模块生成
mod声明,使用::表示路径。 - Python 生成
from ... import ...语句。 - C 内联函数定义并添加注释标明来源。
- TypeScript 内联函数并添加注释标明来源模块。
文件命名约定
Auto 遵循以下文件到模块的映射约定:
| 文件 | 模块路径 |
|---|---|
src/main.auto | (二进制入口点) |
src/utils.auto | utils |
src/network/mod.auto | network(目录模块) |
src/network/server.auto | network::server |
src/network/client.auto | network::client |
每个目录必须有一个 mod.auto 文件作为模块入口点。该文件可以重新导出子模块或直接定义项。
总结
Auto 的包和模块系统提供了在任何规模下组织代码的工具:
| 概念 | Auto | Rust |
|---|---|---|
| 包管理器 | automan | cargo |
| 项目清单 | auto.toml | Cargo.toml |
| 锁文件 | auto.lock | Cargo.lock |
| 定义模块 | mod name { } | mod name { } |
| 设为公开 | pub fn | pub fn |
| 默认可见性 | 私有 | 私有 |
| 导入模块 | use name | mod name; / use name::item; |
| 源文件 | .auto | .rs |
| 目录模块 | mod.auto | mod.rs |
关键设计原则:
默认私有:你必须用
pub显式选择将项公开。这使你的 API 表面是有意且可控的。显式导入:
use关键字使文件间的依赖一目了然。你总能看到函数来自哪里。约定优于配置:文件到模块的映射遵循可预测的规则。
src/utils.auto成为utils模块。src/network/server.auto成为network::server。可重现构建:
auto.lock锁定确切的依赖版本,使构建在不同机器上具有确定性。
在下一章中,我们将探讨 Auto 如何处理集合——你将在组织好的模块中每天使用的数据结构。