Rust Modules vs Python Packages
Rust 模块系统与 Python 包系统对照
What you’ll learn:
modandusevsimport, visibility (pub) vs Python’s convention-based privacy, Cargo.toml vs pyproject.toml, crates.io vs PyPI, and workspaces vs monorepos.
本章将学到什么:mod和use与 Pythonimport的对应关系,pub可见性与 Python 约定式私有的差别,Cargo.toml与pyproject.toml的角色对照,crates.io与 PyPI 的区别,以及 Rust workspace 和 Python monorepo 的不同。Difficulty: 🟢 Beginner
难度: 🟢 入门
Python Module System
Python 的模块系统
# Python — files are modules, directories with __init__.py are packages
# myproject/
# ├── __init__.py # Makes it a package
# ├── main.py
# ├── utils/
# │ ├── __init__.py # Makes utils a sub-package
# │ ├── helpers.py
# │ └── validators.py
# └── models/
# ├── __init__.py
# ├── user.py
# └── product.py
# Importing:
from myproject.utils.helpers import format_name
from myproject.models.user import User
import myproject.utils.validators as validators
Python 这套东西的特点是“默认能跑,靠约定成形”。文件天然就是模块,目录里有 __init__.py 就成了包。学起来轻松,结构也灵活。
但灵活的另一面就是边界偏松,公开不公开、应该从哪里导入、哪些名字只给内部用,很多时候都靠团队自觉。
Rust Module System
Rust 的模块系统
#![allow(unused)]
fn main() {
// Rust — mod declarations create the module tree, files provide content
// src/
// ├── main.rs # Crate root — declares modules
// ├── utils/
// │ ├── mod.rs # Module declaration (like __init__.py)
// │ ├── helpers.rs
// │ └── validators.rs
// └── models/
// ├── mod.rs
// ├── user.rs
// └── product.rs
// In src/main.rs:
mod utils; // Tells Rust to look for src/utils/mod.rs
mod models; // Tells Rust to look for src/models/mod.rs
use utils::helpers::format_name;
use models::user::User;
// In src/utils/mod.rs:
pub mod helpers; // Declares and re-exports helpers.rs
pub mod validators; // Declares and re-exports validators.rs
}
graph TD
A["main.rs<br/>(crate root)"] --> B["mod utils"]
A --> C["mod models"]
B --> D["utils/mod.rs"]
D --> E["helpers.rs"]
D --> F["validators.rs"]
C --> G["models/mod.rs"]
G --> H["user.rs"]
G --> I["product.rs"]
style A fill:#d4edda,stroke:#28a745
style D fill:#fff3cd,stroke:#ffc107
style G fill:#fff3cd,stroke:#ffc107
Python equivalent: Think of
mod.rsas__init__.py— it declares what the module exports. The crate root (main.rs/lib.rs) is like your top-level package__init__.py.
和 Python 的类比: 可以把mod.rs看成__init__.py,负责声明模块要暴露什么。crate 根,也就是main.rs或lib.rs,则类似顶层包的__init__.py。
Rust 的模块系统更明确,也更啰嗦一点。模块树不是靠目录结构自动脑补出来的,而是要用 mod 明明白白声明。
这套设计刚上手时会觉得有点拧巴,但好处是边界更稳定,导出关系更清楚,工程越大越有味道。
Key Differences
核心差异
| Concept 概念 | Python | Rust |
|---|---|---|
| Module = file 文件即模块 | ✅ Automatic ✅ 自动成立 | Must declare with mod需要显式 mod 声明 |
| Package = directory 目录即包 | __init__.py | mod.rs |
| Public by default 默认公开 | ✅ Everything ✅ 基本都能拿到 | ❌ Private by default ❌ 默认私有 |
| Make public 如何公开 | _prefix convention靠命名约定 | pub keyword靠 pub 关键字 |
| Import syntax 导入语法 | from x import y | use x::y; |
| Wildcard import 通配导入 | from x import * | use x::*; (discouraged)use x::*;,但一般不鼓励 |
| Relative imports 相对导入 | from . import sibling | use super::sibling; |
| Re-export 重新导出 | __all__ or explicit__all__ 或手工导出 | pub use inner::Thing; |
Visibility — Private by Default
可见性:默认私有
# Python — "we're all adults here"
class User:
def __init__(self):
self.name = "Alice" # Public (by convention)
self._age = 30 # "Private" (convention: single underscore)
self.__secret = "shhh" # Name-mangled (not truly private)
# Nothing stops you from accessing _age or even __secret
print(user._age) # Works fine
print(user._User__secret) # Works too (name mangling)
#![allow(unused)]
fn main() {
// Rust — private is enforced by the compiler
pub struct User {
pub name: String, // Public — anyone can access
age: i32, // Private — only this module can access
}
impl User {
pub fn new(name: &str, age: i32) -> Self {
User { name: name.to_string(), age }
}
pub fn age(&self) -> i32 { // Public getter
self.age
}
fn validate(&self) -> bool { // Private method
self.age > 0
}
}
// Outside the module:
let user = User::new("Alice", 30);
println!("{}", user.name); // ✅ Public
// println!("{}", user.age); // ❌ Compile error: field is private
println!("{}", user.age()); // ✅ Public method (getter)
}
这就是 Python 和 Rust 在“边界感”上的经典差别。Python 更像是在说:大家都是成年人,别乱碰。Rust 则直接说:哪些能碰,哪些不能碰,编译器提前写死。
写小脚本时前者很轻巧,写大工程时后者通常更省心,因为模块接口和内部实现分得更干净。
Crates vs PyPI Packages
crate 与 PyPI 包
Python Packages (PyPI)
Python 包与 PyPI
# Python
pip install requests # Install from PyPI
pip install "requests>=2.28" # Version constraint
pip freeze > requirements.txt # Lock versions
pip install -r requirements.txt # Reproduce environment
Rust Crates (crates.io)
Rust crate 与 crates.io
# Rust
cargo add reqwest # Install from crates.io (adds to Cargo.toml)
cargo add reqwest@0.12 # Version constraint
# Cargo.lock is auto-generated — no manual step
cargo build # Downloads and compiles dependencies
Python 的依赖管理历史包袱比较重,requirements.txt、setup.py、pyproject.toml、poetry、uv 各有各的玩法。Rust 这边基本统一交给 Cargo,体验就利索很多。
至少在“声明依赖、锁版本、复现环境”这件事上,Cargo 默认就把主流程打通了。
Cargo.toml vs pyproject.toml
Cargo.toml 与 pyproject.toml 对照
# Rust — Cargo.toml
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] } # With feature flags
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
[dev-dependencies]
mockall = "0.13"
pyproject.toml 更像 Python 世界逐步统一后的汇总入口,而 Cargo.toml 则从一开始就是 Rust 项目管理的中心。包信息、依赖、特性开关、构建入口,几乎都围着它转。
所以很多 Python 开发者第一次用 Cargo 时会有种感觉:这玩意怎么什么都给安排好了。
Essential Crates for Python Developers
Python 开发者常见库在 Rust 里的对应物
| Python Library Python 库 | Rust Crate Rust crate | Purpose 用途 |
|---|---|---|
requests | reqwest | HTTP client HTTP 客户端 |
json (stdlib)json 标准库 | serde_json | JSON parsing JSON 解析 |
pydantic | serde | Serialization/validation 序列化与部分校验 |
pathlib | std::path (stdlib)std::path 标准库 | Path handling 路径处理 |
os / shutil | std::fs (stdlib)std::fs 标准库 | File operations 文件操作 |
re | regex | Regular expressions 正则表达式 |
logging | tracing / log | Logging 日志 |
click / argparse | clap | CLI argument parsing 命令行参数解析 |
asyncio | tokio | Async runtime 异步运行时 |
datetime | chrono | Date and time 日期时间 |
pytest | Built-in + rstest内置测试 + rstest | Testing 测试 |
dataclasses | #[derive(...)] | Data structures 数据结构派生 |
typing.Protocol | Traits | Structural typing 结构化抽象 |
subprocess | std::process (stdlib)std::process 标准库 | Run external commands 执行外部命令 |
sqlite3 | rusqlite | SQLite |
sqlalchemy | diesel / sqlx | ORM / SQL toolkit ORM / SQL 工具链 |
fastapi | axum / actix-web | Web framework Web 框架 |
这张表并不是说“Python 库 A 完全等于 Rust 库 B”,而是帮忙建立迁移时的直觉地图。
很多时候 Rust 生态的抽象方式和 Python 不完全一样,但先知道该从哪块地找,效率会高很多。
Workspaces vs Monorepos
workspace 与 monorepo
Python Monorepo (typical)
典型的 Python monorepo
# Python monorepo (various approaches, no standard)
myproject/
├── pyproject.toml # Root project
├── packages/
│ ├── core/
│ │ ├── pyproject.toml # Each package has its own config
│ │ └── src/core/...
│ ├── api/
│ │ ├── pyproject.toml
│ │ └── src/api/...
│ └── cli/
│ ├── pyproject.toml
│ └── src/cli/...
# Tools: poetry workspaces, pip -e ., uv workspaces — no standard
Rust Workspace
Rust workspace
# Rust — Cargo.toml at root
[workspace]
members = [
"core",
"api",
"cli",
]
# Shared dependencies across workspace
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# Rust workspace structure — standardized, built into Cargo
myproject/
├── Cargo.toml # Workspace root
├── Cargo.lock # Single lock file for all crates
├── core/
│ ├── Cargo.toml # [dependencies] serde.workspace = true
│ └── src/lib.rs
├── api/
│ ├── Cargo.toml
│ └── src/lib.rs
└── cli/
├── Cargo.toml
└── src/main.rs
# Workspace commands
cargo build # Build everything
cargo test # Test everything
cargo build -p core # Build just the core crate
cargo test -p api # Test just the api crate
cargo clippy --all # Lint everything
Key insight: Rust workspaces are first-class, built into Cargo. Python monorepos require third-party tools (poetry, uv, pants) with varying levels of support. In a Rust workspace, all crates share a single
Cargo.lock, ensuring consistent dependency versions across the project.
关键理解: Rust workspace 是 Cargo 原生支持的一等能力。Python monorepo 往往需要额外工具来拼装,而且不同方案支持程度差异不小。Rust workspace 下所有 crate 共用一个Cargo.lock,依赖版本一致性更容易维持。
如果已经习惯 Python 大仓库那种“一堆工具互相套娃”的日常,第一次用 Rust workspace 时通常会有点舒服过头。因为它本来就是官方设计的一部分,不用再自己东拼西凑。
这也是 Rust 工程化体验比较整齐的一个重要原因。
Exercises
练习
🏋️ Exercise: Module Visibility 🏋️ 练习:模块可见性判断
Challenge: Given this module structure, predict which lines compile and which don’t:
挑战题: 给定下面这个模块结构,判断哪些行可以编译通过,哪些不行:
mod kitchen {
fn secret_recipe() -> &'static str { "42 spices" }
pub fn menu() -> &'static str { "Today's special" }
pub mod staff {
pub fn cook() -> String {
format!("Cooking with {}", super::secret_recipe())
}
}
}
fn main() {
println!("{}", kitchen::menu()); // Line A
println!("{}", kitchen::secret_recipe()); // Line B
println!("{}", kitchen::staff::cook()); // Line C
}
🔑 Solution 🔑 参考答案
- Line A: ✅ Compiles —
menu()ispub
A 行:✅ 可以编译,因为menu()是pub。 - Line B: ❌ Compile error —
secret_recipe()is private tokitchen
B 行:❌ 编译错误,因为secret_recipe()对kitchen外部是私有的。 - Line C: ✅ Compiles —
staff::cook()ispub, andcook()can accesssecret_recipe()viasuper::(child modules can access parent’s private items)
C 行:✅ 可以编译,因为staff::cook()是pub,而且子模块可以通过super::访问父模块的私有项。
Key takeaway: In Rust, child modules can see parent’s privates (like Python’s _private convention, but enforced). Outsiders cannot. This is the opposite of Python where _private is just a hint.
关键点: Rust 里子模块可以看到父模块的私有项,但外部模块不行。和 Python 里 _private 只是个提示不同,Rust 这里是编译器真管。