rust-port

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rust port

Rust 移植

The goal is the Rust a Rust developer would have written to solve the same problem, with the same observable behavior. A line-by-line transliteration (C written in Rust syntax, or Python with
Box<dyn Any>
) compiles but keeps every problem the original had and adds the borrow checker on top. Port the behavior, not the structure.
目标是产出 Rust 开发者为解决同一问题会编写的 Rust,并具有相同的可观察行为。逐行直译(用 Rust 语法书写 C,或用
Box<dyn Any>
编写 Python)虽然能够编译,却会保留原实现的所有问题,并额外背上借用检查器。应移植行为,而不是结构。

1. Understand the original before writing anything

1. 编写代码前先理解原实现

  1. Read the code and its tests, and anything that calls it, if you have it.
  2. List the observable behavior: every public function, its inputs, its outputs, and every way it can fail. Include the quiet parts, which are where ports go wrong: ordering of results, how ties break, what happens on empty input, whitespace and case handling, integer limits, and what counts as an error versus a valid edge case.
  3. Note what is incidental, meaning things the original only does because of its language: manual memory management, out-parameters,
    NULL
    returns, global state, fixed-size buffers, a class that exists only to group functions, or
    isinstance
    checks. These don't carry over.
  4. If the original has a bug or an ambiguity, keep its behavior, flag it in the porting notes, and let the user decide. A port is not the place to silently fix things.
  1. 阅读代码及其测试;如果可以,也阅读所有调用它的代码。
  2. 列出可观察行为:每个公共函数、它的输入、输出以及所有可能的失败方式。还要包含那些不显眼的行为,因为移植时最容易在这些问题上出错:结果顺序、平局如何处理、空输入时会发生什么、空白字符和大小写如何处理、整数边界,以及哪些情况算错误、哪些属于有效的边缘情况。
  3. 记录语言层面的附带行为,也就是原实现仅因其语言特性而存在的内容:手动内存管理、输出参数、
    NULL
    返回值、全局状态、固定大小缓冲区、仅用于组织函数的类,或
    isinstance
    检查。这些内容不应被移植。
  4. 如果原实现存在缺陷或歧义,应保留其行为,在移植说明中指出,并交由用户决定。移植不应成为悄悄修复问题的地方。

2. Port the tests first

2. 先移植测试

Translate the original tests into Rust tests before porting the implementation. Keep one Rust test per original test, with a name that maps back to it, so a reader can check coverage. The tests are the definition of "same behavior". If the original has no tests, write characterization tests from the behavior list in step 1.
在移植实现之前,先将原有测试翻译为 Rust 测试。每个原有测试都保留为一个对应的 Rust 测试,并使用能够映射回原测试的名称,方便读者检查覆盖率。测试定义了“相同行为”的含义。如果原实现没有测试,则根据第 1 步中的行为列表编写特征化测试。

3. Design the Rust API, then implement it

3. 先设计 Rust API,再实现

Decide the public types and signatures from the behavior list, not from the original's shape. Then implement against the ported tests.
根据行为列表,而不是原实现的结构,确定公共类型和函数签名。然后按照移植后的测试进行实现。

From C and C++

从 C 和 C++ 移植

In the originalIn Rust
malloc
/
free
,
new
/
delete
,
create
/
destroy
pairs
Owned types (
String
,
Vec
,
Box
) dropped automatically;
Drop
for anything else
NULL
meaning "absent"
Option<T>
Error codes (
-1
,
ERR_*
defines),
errno
Result<T, E>
with an error enum, one variant per code, implementing
Display
and
std::error::Error
Out-parameters (
int *err_line
,
long *out
)
Return values: put the data in the
Ok
value or the error variant
Flag bitmasks (
FLAG_A | FLAG_B
)
An options struct with named fields and
Default
, or the
bitflags
crate; never a raw
u32
char *
plus length,
strcmp
,
strcasecmp
&str
/
String
,
==
,
eq_ignore_ascii_case
Pointer plus count, iterator by indexSlices, iterators
Fixed-size buffers and their truncationGrowable types; drop the limit unless the limit is behavior
#define
constants and macros
const
, enums, and functions
Global or static mutable stateA struct the caller owns
strtol
and friends
str::parse
with the matching integer type; map parse failures to your error enum
Don't write
unsafe
or use raw pointers unless the port must talk to C through FFI, and then confine it as the
rust-best-practices
skill describes.
在原实现中在 Rust 中
malloc
/
free
、
new
/
delete
、
create
/
destroy
配对
所有权类型(
String
、
Vec
、
Box
)会自动释放;其他资源使用
Drop
表示“不存在”的
NULL
Option<T>
错误码(
-1
、
ERR_*
宏)、
errno
Result<T, E>
,其中 E 是错误 enum,每个错误码对应一个变体,并实现
Display
和
std::error::Error
输出参数(
int *err_line
、
long *out
)
返回值:将数据放入
Ok
值或错误变体中
标志位掩码(
FLAG_A | FLAG_B
)
使用带命名字段并实现
Default
的选项 struct,或使用
bitflags
crate;绝不使用原始
u32
char *
加长度、
strcmp
、
strcasecmp
&str
/
String
、
==
、
eq_ignore_ascii_case
指针加数量、按索引迭代切片、迭代器
固定大小缓冲区及其截断行为可增长类型;除非大小限制本身属于行为,否则移除该限制
#define
常量和宏
const
、enum 和函数
全局或静态可变状态由调用方拥有的 struct
strtol
等函数
使用
str::parse
并匹配相应的整数类型;将解析失败映射到错误 enum
除非移植必须通过 FFI 与 C 通信,否则不要编写
unsafe
或使用原始指针;必须使用时,应按照
rust-best-practices
skill 的说明将其限制在最小范围内。

From Python

从 Python 移植

In the originalIn Rust
An exception hierarchyOne error enum (or one per area) with a variant per exception class; data attributes become variant fields
None
as a value
Option<T>
Strings used as a closed set (
kind in ("build", "test")
)
An enum, with
FromStr
and
Display
for the text form
Dicts or classes used as recordsStructs with named fields
dict
iteration order (insertion order is guaranteed)
Vec
or an ordered map when order is observable; a plain
HashMap
silently changes output order
Duck typing,
isinstance
Traits or enums
Unbounded
int
A sized integer chosen on purpose, with overflow handled (
checked_*
) where input can be large
sorted(..., key=...)
sort_by_key
or
sort_by
; both are stable, like Python's
str.splitlines()
,
strip()
,
isalnum()
lines()
,
trim()
,
char::is_alphanumeric
; check edge cases such as
\r\n
, Unicode, and empty strings against the original
try
/
except
around a parse
?
with a mapped error, or
match
on the
Result
在原实现中在 Rust 中
异常继承体系一个错误 enum(也可按领域拆分为多个),每个异常类对应一个变体;数据属性转换为变体字段
作为值使用的
None
Option<T>
用作封闭集合的字符串(
kind in ("build", "test")
)
enum,并为文本形式实现
FromStr
和
Display
用作记录结构的字典或类带命名字段的 struct
字典迭代顺序(保证为插入顺序)当顺序可观察时使用
Vec
或有序映射;普通
HashMap
会静默改变输出顺序
Duck typing、
isinstance
trait 或 enum
无界
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

From Go, JavaScript, and TypeScript

从 Go、JavaScript 和 TypeScript 移植

  • Go
    (value, error)
    returns become
    Result
    ;
    nil
    becomes
    Option
    ; interfaces become traits; goroutines and channels become threads with
    std::sync::mpsc
    or async tasks, depending on what the original needs.
  • JavaScript
    null
    and
    undefined
    become
    Option
    ; thrown errors become
    Result
    ; plain objects become structs; numbers become a sized integer or
    f64
    , chosen on purpose.
  • Go 的
    (value, error)
    返回值转换为
    Result
    ;
    nil
    转换为
    Option
    ;接口转换为 trait;goroutine 和 channel 根据原实现的需求转换为使用
    std::sync::mpsc
    的线程或异步任务。
  • JavaScript 的
    null
    和
    undefined
    转换为
    Option
    ;抛出的错误转换为
    Result
    ;普通对象转换为 struct;数字有意识转换为定长整数或
    f64
    。

4. Keep the port proportionate

4. 保持移植规模合理

A port that matches the original on every possible input is usually the wrong goal. Match it on the inputs it actually receives, and use Rust's standard library for the rest.
  • Don't reimplement the source language's runtime. Use
    str::lines
    ,
    str::trim
    ,
    str::parse
    , and
    char
    methods, not hand-written copies of Python's
    splitlines
    ,
    strip
    ,
    int()
    , or
    repr()
    , or of C's locale-dependent
    isspace
    and
    strtol
    . Where Rust's behavior differs only on exotic input, such as Unicode digits, non-ASCII whitespace, or unbounded integers, list the difference in the porting notes instead of coding around it. Write a compatibility shim only when the user says those inputs occur.
  • Don't grow the API. Port the public surface the original has, plus the standard trait impls Rust callers expect (
    Debug
    ,
    Clone
    ,
    Display
    ,
    Error
    ,
    FromStr
    ,
    Default
    , and
    IntoIterator
    for collections). Skip builders,
    Index
    impls,
    FromIterator
    ,
    Extend
    , and custom iterator types unless the original's callers need them.
  • Keep the algorithm unless it's wrong. Swap in a different algorithm only to fix a real problem, such as quadratic behavior on realistic input, and say so in the notes.
  • Check the size. Count code lines, excluding tests, comments, and doc comments. A port from C or C++ usually lands near the original's size. A port from Python or JavaScript usually lands at two to three times the original, because types, error enums, and their trait impls are explicit in Rust. Much more than that usually means one of the three habits above.
Differential testing against the original is worth doing, but a few dozen realistic and edge-case inputs answer the question. A fuzzing harness over millions of inputs usually just finds exotic divergences that belong in the notes.
要求移植在所有可能输入上都与原实现一致,通常并不是正确目标。应匹配实际接收到的输入,其余部分使用 Rust 标准库实现。
  • 不要重新实现源语言的运行时。 使用
    str::lines
    、
    str::trim
    、
    str::parse
    和
    char
    方法,不要手写 Python 的
    splitlines
    、
    strip
    、
    int()
    或
    repr()
    ,也不要重写 C 中依赖 locale 的
    isspace
    和
    strtol
    。如果 Rust 的行为仅在特殊输入上存在差异,例如 Unicode 数字、非 ASCII 空白字符或无界整数,应在移植说明中列出差异,而不是编写代码规避。只有当用户明确表示会出现这些输入时,才编写兼容性 shim。
  • 不要扩大 API。 移植原实现已有的公共接口,并补充 Rust 调用方期望的标准 trait 实现(
    Debug
    、
    Clone
    、
    Display
    、
    Error
    、
    FromStr
    、
    Default
    ,以及集合的
    IntoIterator
    )。除非原实现的调用方确实需要,否则不要添加 builder、
    Index
    实现、
    FromIterator
    、
    Extend
    或自定义迭代器类型。
  • 除非算法本身有问题,否则保留原算法。 只有为了解决真实问题时才替换算法,例如真实输入下存在二次方复杂度,并应在说明中指出。
  • 检查代码规模。 统计代码行数时,不包括测试、注释和文档注释。从 C 或 C++ 移植时,代码规模通常接近原实现。从 Python 或 JavaScript 移植时,通常会达到原实现的两到三倍,因为 Rust 需要显式编写类型、错误 enum 及其 trait 实现。规模远超这一范围,通常意味着出现了上述三种不良习惯之一。
可以进一步进行差分测试,但几十个真实输入和边缘输入通常就足以回答是否保持一致的问题。对数百万个输入运行 fuzzing 框架,通常只会发现应在说明中记录的特殊差异。

5. Verify

5. 验证

Run these if a Rust toolchain is available, and fix everything before reporting:
bash
cargo build
cargo test
cargo clippy --all-targets -- -D warnings
Every ported test must pass. If the original's tests can be run too, run them and compare results on the same inputs.
如果可以使用 Rust 工具链,请运行以下命令,并在报告结果前修复所有问题:
bash
cargo build
cargo test
cargo clippy --all-targets -- -D warnings
每个移植后的测试都必须通过。如果原有测试也可以运行,则一并运行,并在相同输入下比较结果。

6. Write the porting notes

6. 编写移植说明

Alongside the code, write a short
PORTING.md
that a reviewer can check in a few minutes:
  • API mapping: a table from each original function or type to its Rust equivalent.
  • Test mapping: each original test and the Rust test that covers it.
  • Deliberate differences: anything that now behaves differently and why, such as an out-of-memory error that no longer exists, a fixed buffer limit that's gone, or exotic input where Rust's standard library behaves differently from the original's.
  • Preserved quirks: odd behavior kept on purpose, so nobody "fixes" it by accident.
  • Verification: which commands you ran and their result, or a plain statement that you couldn't run them.
For the style of the Rust itself, follow the
rust-best-practices
skill if it is installed.
在代码旁编写一份简短的
PORTING.md
,以便审阅者能在几分钟内完成检查:
  • API 映射: 使用表格将每个原有函数或类型映射到对应的 Rust 实现。
  • 测试映射: 列出每个原有测试及其对应的 Rust 测试。
  • 有意保留的差异: 记录所有行为发生变化的细节及其原因,例如不再存在的内存不足错误、被移除的固定缓冲区限制,以及 Rust 标准库对特殊输入的处理与原实现不同的情况。
  • 保留的特殊行为: 记录有意保留的异常行为,避免其他人无意中将其“修复”。
  • 验证: 列出执行过的命令及其结果,或者直接说明无法运行这些命令。
对于 Rust 代码本身的风格,如果已安装
rust-best-practices
skill,请遵循该 skill。