rust-implement

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rust Implementation Discipline

Rust代码实现准则

Write code in passes. Experts don't produce perfect code in one shot -- they design, implement, review, and simplify. Follow this process for every module you write.

采用分阶段方式编写代码。专家不会一次性写出完美代码——他们会先设计、实现、评审,再简化。编写每个模块时都遵循此流程。

Pass 1: Design Types and Signatures

阶段1:设计类型与函数签名

Before writing any implementation, write ONLY the type definitions and function signatures. No bodies. No logic.
Ask yourself these questions before moving on:
  • Can any enum state represent an invalid combination? If yes, restructure so invalid states are unrepresentable.
  • Are all parameters self-documenting? Replace
    bool
    params with enums (Transformation 2).
  • Does every struct that will be serialized or compared use
    BTreeMap
    , not
    HashMap
    (Transformation 3)?
  • Do config structs derive
    Default
    (Transformation 6)?
  • Would a caller confuse the order of String parameters? Use newtypes.
  • Does any struct have 2+
    Option<T>
    fields where only one combination is valid at a time? Convert to an enum (Transformation 8).
  • Does any function take 4+ parameters? Replace with an args struct (Transformation 9).
Do not proceed to Pass 2 until the types are right. Types are the architecture.

在编写任何实现代码之前,只编写类型定义和函数签名。不要写函数体,不要写逻辑。
继续下一步之前,请自问以下问题:
  • 枚举的任何状态是否能表示无效组合?如果是,重构使其无法表示无效状态。
  • 所有参数是否都是自文档化的?将
    bool
    参数替换为枚举(转换2)。
  • 所有需要序列化或比较的结构体是否都使用
    BTreeMap
    而非
    HashMap
    (转换3)?
  • 配置结构体是否派生了
    Default
    (转换6)?
  • 调用者是否会混淆String参数的顺序?使用新类型(newtype)。
  • 是否存在结构体包含2个及以上
    Option<T>
    字段,且同一时间只有一种组合有效?将其转换为枚举(转换8)。
  • 是否存在函数包含4个及以上参数?使用参数结构体替换(转换9)。
在类型设计正确之前,不要进入阶段2。类型就是架构。

Pass 2: Implement

阶段2:实现代码

Fill in the function bodies. As you write each function, apply these rules:
  • Every
    ?
    gets
    .context("failed to [verb] [noun]")
    -- no exceptions (Transformation 1).
  • For library code: use
    thiserror
    for error enums. For application code: use
    anyhow
    .
  • Use
    Cow<str>
    when a function sometimes borrows and sometimes allocates (Transformation 7).
  • Return named structs, not tuples, from any function with 2+ return values (Transformation 5).
  • Exhaustive match arms -- no
    _ =>
    wildcards (Transformation 4).
填充函数体。编写每个函数时,遵循以下规则:
  • 每个
    ?
    都必须搭配
    .context("failed to [verb] [noun]")
    ——无一例外(转换1)。
  • 库代码:使用
    thiserror
    定义错误枚举。应用代码:使用
    anyhow
  • 当函数有时需要借用、有时需要分配内存时,使用
    Cow<str>
    (转换7)。
  • 任何返回2个及以上值的函数,都返回命名结构体而非元组(转换5)。
  • 匹配分支要穷尽——不要使用
    _ =>
    通配符(转换4)。

Thread-Through Plumbing

穿透式管线

When adding a new field that needs to reach execution sites, trace the full path and explicitly name every intermediate layer before writing code. For each hop, state the module and struct/function that carries the value. If you skip a layer, the field silently disappears at runtime.
当添加一个需要传递到执行位置的新字段时,在编写代码前先追踪完整路径,并明确命名每一个中间层。对于每一次传递,说明承载该值的模块和结构体/函数。如果跳过某一层,该字段会在运行时无声消失。

Drop/Teardown Precision

Drop/清理操作的精确性

  • Drop lock guards before any
    .await
    -- a held
    MutexGuard
    across an await blocks the executor.
  • Close stdin explicitly in
    Drop
    impls. Brief wait, then fallback kill.
  • Use
    kill_on_drop(true)
    for child processes that must not outlive their parent.
  • Order cleanup deterministically: clear listeners -> drain tasks -> drop resources.
  • 在任何
    .await
    之前释放锁守卫(lock guard)——在await期间持有
    MutexGuard
    会阻塞执行器。
  • Drop
    实现中显式关闭标准输入(stdin)。短暂等待后,再强制终止。
  • 对于不能比父进程存活更久的子进程,使用
    kill_on_drop(true)
  • 确定性地排序清理步骤:清除监听器 -> 排空任务 -> 释放资源。

Defensive Serialization

防御式序列化

  • BEGIN IMMEDIATE
    not
    BEGIN
    for SQLite write transactions (prevents
    SQLITE_BUSY_SNAPSHOT
    ).
  • Serialize lock acquisition rather than adding retries.
  • ON CONFLICT DO NOTHING
    over read-then-write for idempotent inserts.
  • SQLite写入事务使用
    BEGIN IMMEDIATE
    而非
    BEGIN
    (避免
    SQLITE_BUSY_SNAPSHOT
    错误)。
  • 序列化锁获取操作,而非添加重试逻辑。
  • 对于幂等插入操作,使用
    ON CONFLICT DO NOTHING
    而非先读取再写入。

Orphan Event Handling

孤立事件处理

When events arrive for entities that no longer exist (dead agents, closed threads), handle explicitly. Never silently drop -- log a warning and clean up stale state. Panicking on an orphan event is always wrong.
当事件到达已不存在的实体(已终止的agent、已关闭的线程)时,要显式处理。绝不能无声丢弃——记录警告并清理过期状态。对孤立事件触发panic永远是错误的。

Ownership Decision Tree

所有权决策树

Walk this tree in order when you need shared access to data:
  1. Can you pass a reference? Use
    &T
    . Zero cost, zero complexity.
  2. Might it be owned or borrowed? Use
    Cow<'_, str>
    . Zero-cost when borrowed.
  3. Multiple owners across sync boundaries? Use
    Arc<T>
    . Prefer
    Arc::clone(&x)
    over
    x.clone()
    .
  4. Multiple owners AND mutability? Use
    Arc<Mutex<T>>
    or
    Arc<RwLock<T>>
    . Choose
    RwLock
    when reads vastly outnumber writes.
  5. Fire-and-forget spawn? Use
    move
    closure with owned data.
Every
.clone()
is a decision point. Ask: "Is this clone necessary, or can I restructure to borrow?"
当需要共享数据访问时,按以下顺序决策:
  1. 能否传递引用? 使用
    &T
    。零开销,零复杂度。
  2. 可能是所有权或借用? 使用
    Cow<'_, str>
    。借用时零开销。
  3. 跨同步边界的多个所有者? 使用
    Arc<T>
    。优先使用
    Arc::clone(&x)
    而非
    x.clone()
  4. 多个所有者且需要可变性? 使用
    Arc<Mutex<T>>
    Arc<RwLock<T>>
    。当读取次数远多于写入时,选择
    RwLock
  5. 即发即弃(fire-and-forget)的任务? 使用
    move
    闭包获取所有权数据。
每一个
.clone()
都是一个决策点。自问:“这个clone是必要的吗?还是可以重构为借用?”

Error Philosophy

错误处理哲学

ContextToolWhy
Application code (main, CLI, tests)
anyhow::Result
+
.context()
Rich error chains, no boilerplate
Library code (crates consumed by others)
thiserror
enums
Typed, matchable, callers can branch on variants
Error enum design: user-facing messages tell the user what to do, not what went wrong internally.
Box<T>
large payloads. Classify every variant explicitly in
is_retryable()
-- no wildcards.
场景工具原因
应用代码(main、CLI、测试)
anyhow::Result
+
.context()
丰富的错误链,无需样板代码
库代码(供其他项目使用的crates)
thiserror
枚举
类型化、可匹配,调用者可根据变体分支处理
错误枚举设计:面向用户的消息要告诉用户该做什么,而非内部发生了什么错误。对大型负载使用
Box<T>
。在
is_retryable()
中显式分类每个变体——不要使用通配符。

Async Decisions

异步决策

When to
Box::pin
:
When thin async wrappers inline large callee futures into their state machine, causing stack pressure. Wrap the inner call:
Box::pin(self.inner_method(args)).await
.
NeedChannelWhy
One response back
oneshot
Exactly one value, then done
Stream of events
mpsc
Multiple producers, single consumer
Latest value only
watch
Receivers always see the most recent value
Broadcast to all
broadcast
Every receiver gets every message
Shutdown: use
CancellationToken
for hierarchical shutdown. Never rely on
Drop
for ordered async cleanup. Never hold a
MutexGuard
across an
.await
point.

何时使用
Box::pin
当轻量异步包装器将大型被调用方future内联到其状态机中,导致栈压力时。包装内部调用:
Box::pin(self.inner_method(args)).await
需求通道原因
返回单个响应
oneshot
仅传递一个值,之后结束
事件流
mpsc
多生产者,单消费者
仅需最新值
watch
接收者始终看到最新值
广播到所有接收者
broadcast
每个接收者都会收到所有消息
关闭:使用
CancellationToken
进行分层关闭。绝不要依赖
Drop
进行有序异步清理。绝不要在
.await
点持有
MutexGuard

Pass 3: Simplify

阶段3:简化代码

Review your code as if you're trying to REMOVE things, not add them.
Three diagnostic questions:
  1. Is any parameter just forwarded through a call chain? Remove it, use ambient access.
  2. Is any field or function unused? Delete it.
  3. Is any abstraction unjustified? (single-use helper, wrapper that adds nothing) Inline it.
"Rewrite, don't rewire" principle: When the bug is in a function's internal logic, rewrite the body with explicit checks. Don't delegate to an existing API that happens to produce the correct result for now -- the explicit version is more auditable and survives upstream changes.
If you added more code than the task strictly requires, something is wrong. Cut it.

像要删除代码一样评审你的代码,而不是添加代码。
三个诊断问题:
  1. 是否有参数只是在调用链中转发? 删除它,使用环境访问。
  2. 是否有未使用的字段或函数? 删除它。
  3. 是否有不必要的抽象?(单次使用的辅助函数、无任何附加功能的包装器)将其内联。
“重写,而非重连”原则: 当函数内部逻辑存在bug时,重写函数体并添加显式检查。不要委托给当前恰好能产生正确结果的现有API——显式版本更易于审计,且能在 upstream 变更时保持稳定。
如果你添加的代码超出了任务严格需要的范围,那一定有问题。删掉多余的代码。

Pass 4: Verify

阶段4:验证

Run the bundled lint script on your code:
bash
bash ${SKILL_DIR}/scripts/lint.sh <your-file.rs>
Fix every ERROR. Review every WARNING. Then do the manual checklist:
[ ] Every ? has .context("failed to [verb] [noun]")
[ ] No unwrap() outside #[cfg(test)]
[ ] BTreeMap where output is serialized or compared
[ ] No bool parameters -- use enums
[ ] Match arms exhaustive -- no _ => wildcards
[ ] Config structs derive Default
[ ] No single-use helper functions -- inline if called once
[ ] Module under 500 lines (excluding tests)
[ ] No unnecessary .clone() -- prefer borrowing
[ ] Return structs, not tuples, for multi-value returns
[ ] Cow<str> where allocation is conditional
[ ] serde attrs present: rename_all, default, deny_unknown_fields as needed
[ ] No struct with 2+ Option<T> fields that should be an enum
[ ] No function with 4+ parameters (use args struct)
[ ] Lock guards dropped before any .await
[ ] Events for dead/missing entities handled explicitly
Fix every violation before presenting the code.

在你的代码上运行捆绑的lint脚本:
bash
bash ${SKILL_DIR}/scripts/lint.sh <your-file.rs>
修复所有ERROR。评审所有WARNING。然后完成手动检查清单:
[ ] 每个?都带有.context("failed to [verb] [noun]")
[ ] 除#[cfg(test)]外,没有unwrap()
[ ] 需要序列化或比较的地方使用BTreeMap
[ ] 没有bool参数——使用枚举
[ ] 匹配分支穷尽——没有_ =>通配符
[ ] 配置结构体派生了Default
[ ] 没有单次使用的辅助函数——如果只调用一次则内联
[ ] 模块代码不超过500行(测试代码除外)
[ ] 没有不必要的.clone()——优先借用
[ ] 多值返回使用结构体而非元组
[ ] 条件分配时使用Cow<str>
[ ] 存在serde属性:根据需要使用rename_all、default、deny_unknown_fields
[ ] 没有包含2个及以上应改为枚举的Option<T>字段的结构体
[ ] 没有包含4个及以上参数的函数(使用参数结构体)
[ ] 在任何.await之前释放锁守卫
[ ] 显式处理已终止/不存在实体的事件
在提交代码之前修复所有违规项。

Quick Reference: The 9 Transformations

快速参考:9种转换

Each transformation shows the exact delta between first-draft and production-grade.
每种转换展示了初稿与生产级代码之间的精确差异。

1.
.context()
on Every
?
Operator

1. 每个
?
操作符搭配
.context()

BEFORE:
rust
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
AFTER:
rust
let content = std::fs::read_to_string(path)
    .context("failed to read config file")?;
let config: Config = toml::from_str(&content)
    .context("failed to parse TOML config")?;
Pattern:
"failed to [verb] [noun]"
. The upstream error describes itself -- your job is to name the operation that broke.
BEFORE:
rust
let content = std::fs::read_to_string(path)?;
let config: Config = toml::from_str(&content)?;
AFTER:
rust
let content = std::fs::read_to_string(path)
    .context("failed to read config file")?;
let config: Config = toml::from_str(&content)
    .context("failed to parse TOML config")?;
模式:
"failed to [verb] [noun]"
。上游错误会描述自身——你的任务是指出失败的操作。

2. Enums Over Booleans

2. 用枚举替代布尔值

BEFORE:
rust
fn create_sandbox(network: bool, writable: bool) -> Sandbox {
AFTER:
rust
fn create_sandbox(network: NetworkMode, access: AccessLevel) -> Sandbox {
foo(true, false)
is meaningless. Replace with enums so callsites read
NetworkMode::Restricted, AccessLevel::ReadOnly
. When you cannot change the API, add
/*param_name*/
comments before opaque literals.
BEFORE:
rust
fn create_sandbox(network: bool, writable: bool) -> Sandbox {
AFTER:
rust
fn create_sandbox(network: NetworkMode, access: AccessLevel) -> Sandbox {
foo(true, false)
毫无意义。替换为枚举,使调用站点变为
NetworkMode::Restricted, AccessLevel::ReadOnly
。当无法修改API时,在不透明字面量前添加
/*param_name*/
注释。

3. BTreeMap for Serialized or Compared Data

3. 序列化或比较数据使用BTreeMap

BEFORE:
HashMap<String, Rule>
-- AFTER:
BTreeMap<String, Rule>
HashMap iteration order is random -- diffs become noisy, snapshot tests flake. Use BTreeMap whenever data is serialized, compared in tests, or shown to users.
BEFORE:
HashMap<String, Rule>
-- AFTER:
BTreeMap<String, Rule>
HashMap的迭代顺序是随机的——差异会变得杂乱,快照测试会不稳定。每当数据需要序列化、在测试中比较或展示给用户时,使用BTreeMap。

4. Exhaustive Match Without Wildcards

4. 穷尽匹配,无通配符

BEFORE:
rust
match decision {
    PolicyDecision::Allow => true,
    _ => false,
}
AFTER:
rust
match decision {
    PolicyDecision::Allow => true,
    PolicyDecision::Block { .. } => false,
    PolicyDecision::Rewrite { .. } => false,
    PolicyDecision::Ask { .. } => false,
}
When a new variant is added, the compiler flags every match that needs updating. Wildcards hide this.
BEFORE:
rust
match decision {
    PolicyDecision::Allow => true,
    _ => false,
}
AFTER:
rust
match decision {
    PolicyDecision::Allow => true,
    PolicyDecision::Block { .. } => false,
    PolicyDecision::Rewrite { .. } => false,
    PolicyDecision::Ask { .. } => false,
}
当添加新变体时,编译器会标记所有需要更新的匹配。通配符会隐藏这种情况。

5. Return Structs Over Tuples

5. 返回结构体而非元组

BEFORE:
fn build_command() -> (Vec<String>, Vec<OwnedFd>)
-- caller writes
result.0
AFTER:
fn build_command() -> CommandOutput
-- caller writes
output.args
,
output.preserved_fds
Named fields are self-documenting. Define a struct for any function returning 2+ values.
BEFORE:
fn build_command() -> (Vec<String>, Vec<OwnedFd>)
-- 调用者写
result.0
AFTER:
fn build_command() -> CommandOutput
-- 调用者写
output.args
,
output.preserved_fds
命名字段是自文档化的。为任何返回2个及以上值的函数定义结构体。

6.
Default
Derive on Config Structs

6. 配置结构体派生
Default

rust
#[derive(Default)]
pub struct ServerConfig {
    pub timeout_secs: u64,
    pub max_retries: u32,
    pub bind_addr: String,
}
let config = ServerConfig { timeout_secs: 30, ..Default::default() };
Derive
Default
so callers override only what matters. For non-zero defaults, implement
Default
manually.
rust
#[derive(Default)]
pub struct ServerConfig {
    pub timeout_secs: u64,
    pub max_retries: u32,
    pub bind_addr: String,
}
let config = ServerConfig { timeout_secs: 30, ..Default::default() };
派生
Default
以便调用者仅覆盖需要修改的部分。对于非零默认值,手动实现
Default

7.
Cow<str>
for Conditional Ownership

7. 条件所有权使用
Cow<str>

BEFORE:
rust
fn normalize_path(input: &str) -> String {
    if needs_normalization(input) { input.replace('\\', "/") }
    else { input.to_string() }  // allocates even when unchanged
}
AFTER:
rust
fn normalize_path(input: &str) -> Cow<'_, str> {
    if needs_normalization(input) { Cow::Owned(input.replace('\\', "/")) }
    else { Cow::Borrowed(input) }  // zero-cost when unchanged
}
Cow<str>
avoids the unconditional allocation when only some paths need to allocate.
BEFORE:
rust
fn normalize_path(input: &str) -> String {
    if needs_normalization(input) { input.replace('\\', "/") }
    else { input.to_string() }  // 即使未修改也会分配内存
}
AFTER:
rust
fn normalize_path(input: &str) -> Cow<'_, str> {
    if needs_normalization(input) { Cow::Owned(input.replace('\\', "/")) }
    else { Cow::Borrowed(input) }  // 未修改时零开销
}
Cow<str>
避免了仅部分路径需要分配时的无条件内存分配。

8. Enum-as-Disjoint-Union

8. 用枚举表示不相交并集

BEFORE:
rust
struct Permissions {
    profile_name: Option<String>,       // only for named profiles
    sandbox_policy: Option<SandboxPolicy>, // only for legacy
    file_paths: Option<Vec<PathBuf>>,   // only when sandbox_policy is set
}
AFTER:
rust
enum Permissions {
    Named { profile_name: String },
    Legacy { sandbox_policy: SandboxPolicy, file_paths: Vec<PathBuf> },
}
When a struct has
Option<T>
fields valid only in certain combinations, it permits invalid states at the type level. Convert to an enum where each variant carries only its relevant data. Diagnostic: if you see 2+
Option<T>
fields with
is_some()
/
is_none()
guards, it should be an enum.
BEFORE:
rust
struct Permissions {
    profile_name: Option<String>,       // 仅用于命名配置文件
    sandbox_policy: Option<SandboxPolicy>, // 仅用于遗留系统
    file_paths: Option<Vec<PathBuf>>,   // 仅当sandbox_policy设置时有效
}
AFTER:
rust
enum Permissions {
    Named { profile_name: String },
    Legacy { sandbox_policy: SandboxPolicy, file_paths: Vec<PathBuf> },
}
当结构体包含仅在特定组合下有效的
Option<T>
字段时,它在类型层面允许无效状态。转换为枚举,每个变体仅携带其相关数据。诊断标志:如果看到2个及以上带有
is_some()
/
is_none()
守卫的
Option<T>
字段,就应该改为枚举。

9. Struct-for-Long-Parameter-Lists

9. 用结构体替代长参数列表

BEFORE:
rust
fn spawn_process(
    cmd: &str,
    args: &[String],
    env: &BTreeMap<String, String>,
    cwd: &Path,
    stdin_policy: StdinPolicy,
    sandbox: SandboxPolicy,
    timeout: Duration,
) -> Result<Child> {
AFTER:
rust
struct SpawnArgs<'a> {
    cmd: &'a str,
    args: &'a [String],
    env: &'a BTreeMap<String, String>,
    cwd: &'a Path,
    stdin_policy: StdinPolicy,
    sandbox: SandboxPolicy,
    timeout: Duration,
}

fn spawn_process(args: &SpawnArgs<'_>) -> Result<Child> {
At 4+ parameters, callsites become hard to read and easy to misorder. An args struct names each field at the callsite and makes future parameter additions non-breaking.
BEFORE:
rust
fn spawn_process(
    cmd: &str,
    args: &[String],
    env: &BTreeMap<String, String>,
    cwd: &Path,
    stdin_policy: StdinPolicy,
    sandbox: SandboxPolicy,
    timeout: Duration,
) -> Result<Child> {
AFTER:
rust
struct SpawnArgs<'a> {
    cmd: &'a str,
    args: &'a [String],
    env: &'a BTreeMap<String, String>,
    cwd: &'a Path,
    stdin_policy: StdinPolicy,
    sandbox: SandboxPolicy,
    timeout: Duration,
}

fn spawn_process(args: &SpawnArgs<'_>) -> Result<Child> {
当参数达到4个及以上时,调用站点会变得难以阅读且容易出错。参数结构体在调用站点为每个字段命名,且未来添加参数不会导致破坏性变更。