关于 rust-port
rust-port 是帮助 Rust 开发者将现有程序移植为符合 Rust 惯例实现的技能,重点保持可观察行为而非照搬原有结构。它提供从行为分析、测试迁移到验证和编写移植说明的流程。
主要功能
- 01先梳理原程序的可观察行为
- 02先将原有测试迁移为 Rust 测试
- 03按 Rust 类型和错误模型设计 API
- 04针对 C、C++、Python、Go、JavaScript 和 TypeScript 的映射建议
- 05运行构建、测试与 Clippy 检查
- 06编写 API、测试及差异说明
适用场景
- 01将 C 或 C++ 库移植到 Rust
- 02在 Rust 中重写 Python 或 JavaScript 程序
- 03确保移植版本保留边界行为与结果顺序
- 04为代码审查准备移植记录
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ morganlinton/ beautifulrust --skill rust-port安装后在 Claude Code 中输入/rust-port确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/port-c-config-parser/case.yamlplace-files.shprompt.mdevals/port-c-config-parser/files/config.cconfig.htest_config.cevals/port-c-config-parser/graders/01-builds-and-tests-pass.md02-clippy-clean.md03-no-unsafe.md04-every-test-ported.md05-errors-as-result.md06-error-implements-error.md07-option-for-absent.md08-typed-flags.md09-behavior-preserved.md10-porting-notes.md11-proportionate.mdevals/port-python-task-graph/case.yamlevals/port-python-task-graph/files/taskgraph.pytest_taskgraph.pyevals/port-python-task-graph/graders/01-builds-and-tests-pass.md02-clippy-clean.md03-every-test-ported.md04-exceptions-as-error-enum.md05-kind-is-enum.md06-priority-is-option.md07-definition-order-preserved.md08-order-and-cycle-match.md09-summary-every-kind-in-order.md10-porting-notes.md详细介绍
Rust 移植
目标是写出 Rust 开发者会用 Rust 编写的代码来解决同一个问题,并保持相同的可观察行为。逐行转写(用 Rust 语法写 C,或用 Box<dyn Any> 写 Python)虽然能编译,却会保留原实现中的所有问题,还会额外带上借用检查器。移植行为,而不是结构。
1. 动笔前先理解原实现
- 阅读代码及其测试,以及所有调用它的代码(如果有)。
- 列出可观察行为:每个公共函数、它的输入和输出,以及所有可能的失败方式。也要包括那些容易被忽略的细节,因为移植最容易在这些地方出错:结果顺序、平局时如何处理、空输入时会发生什么、空白和大小写的处理方式、整数范围,以及哪些情况算错误、哪些是有效的边界情况。
- 标记哪些行为只是偶然实现细节,也就是原实现仅因其语言而采用的做法:手动内存管理、输出参数、返回
NULL、全局状态、定长缓冲区、仅用于归类函数的类,或isinstance检查。这些都不需要照搬。 - 如果原实现有缺陷或存在歧义,就在移植说明中标明并保留其行为,让用户决定。移植时不要悄悄修复问题。
2. 先移植测试
先将原测试移植为 Rust 测试,再移植实现。每个原测试对应一个 Rust 测试,并使用能对应回原测试的名称,以便读者检查覆盖情况。若原实现没有测试,则根据第 1 步列出的行为编写特征测试。
3. 先设计 Rust API,再实现
根据行为列表决定公共类型和签名,不要照搬原实现的形式。然后针对移植后的测试编写实现。
从 C 和 C++ 移植
| 原实现中的内容 | Rust 中的做法 |
|---|---|
malloc/free、new/delete、create/destroy 配对 | 使用所有权类型(String、Vec、Box),它们会自动释放;其他情况使用 Drop |
用 NULL 表示“缺失” | Option<T> |
错误码(-1、ERR_* 定义)、errno | 使用带错误枚举的 Result<T, E>,每个错误码对应一个变体,并实现 Display 和 std::error::Error |
输出参数(int *err_line、long *out) | 使用返回值:将数据放入 Ok 值或错误变体中 |
标志位掩码(FLAG_A | FLAG_B) | 使用带命名字段和 Default 的选项结构体,或使用 bitflags crate;不要使用原始 u32 |
char * 加长度、strcmp、strcasecmp | 使用 &str/String、==、eq_ignore_ascii_case |
| 指针加计数、按索引遍历 | 使用切片、迭代器 |
| 定长缓冲区及其截断行为 | 使用可增长类型;除非长度限制本身就是行为,否则移除限制 |
#define 常量和宏 | 使用 const、枚举和函数 |
| 全局或静态可变状态 | 使用由调用方持有的结构体 |
strtol 等函数 | 使用 str::parse 和匹配的整数类型;将解析失败映射到错误枚举 |
除非移植必须通过 FFI 与 C 交互,否则不要编写 unsafe 或使用原始指针;如确有必要,请按照 rust-best-practices 技能中的说明将其限制在局部范围内。
从 Python 移植
| 原语言中的写法 | Rust 中的写法 |
|---|---|
| 异常层次结构 | 使用一个错误枚举(或每个领域一个),为每个异常类设置一个变体;数据属性变为变体字段 |
None 作为值 | Option<T> |
将字符串用作封闭集合(kind in ("build", "test")) | 使用枚举,并为文本形式实现 FromStr 和 Display |
| 将字典或类用作记录 | 使用带命名字段的结构体 |
dict 的迭代顺序(保证为插入顺序) | 当顺序可观察时,使用 Vec 或有序映射;普通 HashMap 会悄然改变输出顺序 |
鸭子类型、isinstance | Trait 或枚举 |
无界 int | 有意选择固定宽度的整数;输入可能很大时,处理溢出(checked_*) |
sorted(..., key=...) | sort_by_key 或 sort_by;与 Python 一样,两者都是稳定排序 |
str.splitlines()、strip()、isalnum() | lines()、trim()、char::is_alphanumeric;应针对原实现检查 \r\n、Unicode 和空字符串等边界情况 |
围绕解析操作使用 try/except | 使用带错误映射的 ?,或对 Result 使用 match |
从 Go、JavaScript 和 TypeScript 移植
- Go 的
(value, error)返回值变为Result;nil变为Option;接口变为 trait;goroutine 和 channel 则根据原实现的需求,变为使用std::sync::mpsc的线程或异步任务。 - JavaScript 的
null和undefined变为Option;抛出的错误变为Result;普通 对象变为结构体;数字变为有意选择的固定宽度整数或f64。
4. 保持移植规模适度
力求在所有可能的输入上都与原实现一致,通常不是正确的目标。 只需在实际会收到的输入上保持一致,其余情况使用 Rust 标准库即可。
- 不要重新实现源语言的运行时。 使用
str::lines、str::trim、str::parse和char方法,而不要手写 Python 的splitlines、strip、int()或repr()的副本,也不要手写 C 中依赖区域设置的isspace和strtol。如果 Rust 的行为只在 Unicode 数字、非 ASCII 空白字符或无界整数等罕见输入上有所不同,应在移植说明中列出差异,而不是专门编写代码来处理。只有在用户说明会遇到这些输入时,才编写兼容层。 - 不要扩大 API。 移植原实现的公开接口,并实现 Rust 调用方通常期望的标准
trait(集合的
Debug、Clone、Display、Error、FromStr、Default和IntoIterator)。除非原实现的调用方需要,否则不要添加构建器、Index实现、FromIterator、Extend和自定义迭代器类型。 - 保留算法,除非它有问题。 只有为了解决实际问题(例如对真实输入的处理复杂度为二次方)时,才换用其他算法,并在说明中解释原因。
- 检查规模。 统计代码行数,不包括测试、注释和文档注释。从 C 或 C++ 移植的代码通常与原实现规模相近。从 Python 或 JavaScript 移植的代码通常是原实现的两到三倍,因为 Rust 需要明确写出类型、错误枚举及其 trait 实现。规模大得多时,通常是上面三种习惯之一导致的。
与原实现进行差分测试值得一做,但几十个真实场景和边界情况的输入就足以回答问题。使用数百万个输入进行模糊测试,通常只会发现应记录在说明中的罕见差异。
5. 验证
如果有 Rust 工具链,请运行以下命令,并在报告前修复所有问题:
cargo build
cargo test
cargo clippy --all-targets -- -D warnings
所有已移植的测试都必须通过。如果也能运行原实现的测试,请一并运行,并使用相同输入比较结果。
6. 编写移植说明
在代码之外,编写一份简短的 PORTING.md,方便审阅者在几分钟内检查:
- API 映射: 列出每个原始函数或类型及其对应的 Rust 实现的表格。
- 测试映射: 列出每个原始测试及覆盖它的 Rust 测试。
- 刻意做出的差异: 说明现在行为有所不同之处及原因,例如不再存在的内存不足错误、已取消的固定缓冲区限制,或 Rust 标准库对特殊输入的处理与原实现不同。
- 保留的特性: 列出有意保留的奇怪行为,以免有人意外将其“修正”。
- 验证: 列出运行过的命令及其结果;如果无法运行,请直接说明。
Rust 代码的风格请遵循 rust-best-practices skill(如果已安装)。