rust-implement
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRust 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 params with enums (Transformation 2).
bool - Does every struct that will be serialized or compared use , not
BTreeMap(Transformation 3)?HashMap - Do config structs derive (Transformation 6)?
Default - Would a caller confuse the order of String parameters? Use newtypes.
- Does any struct have 2+ fields where only one combination is valid at a time? Convert to an enum (Transformation 8).
Option<T> - 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.
在编写任何实现代码之前,只编写类型定义和函数签名。不要写函数体,不要写逻辑。
继续下一步之前,请自问以下问题:
- 枚举的任何状态是否能表示无效组合?如果是,重构使其无法表示无效状态。
- 所有参数是否都是自文档化的?将参数替换为枚举(转换2)。
bool - 所有需要序列化或比较的结构体是否都使用而非
BTreeMap(转换3)?HashMap - 配置结构体是否派生了(转换6)?
Default - 调用者是否会混淆String参数的顺序?使用新类型(newtype)。
- 是否存在结构体包含2个及以上字段,且同一时间只有一种组合有效?将其转换为枚举(转换8)。
Option<T> - 是否存在函数包含4个及以上参数?使用参数结构体替换(转换9)。
在类型设计正确之前,不要进入阶段2。类型就是架构。
Pass 2: Implement
阶段2:实现代码
Fill in the function bodies. As you write each function, apply these rules:
- Every gets
?-- no exceptions (Transformation 1)..context("failed to [verb] [noun]") - For library code: use for error enums. For application code: use
thiserror.anyhow - Use when a function sometimes borrows and sometimes allocates (Transformation 7).
Cow<str> - Return named structs, not tuples, from any function with 2+ return values (Transformation 5).
- Exhaustive match arms -- no wildcards (Transformation 4).
_ =>
填充函数体。编写每个函数时,遵循以下规则:
- 每个都必须搭配
?——无一例外(转换1)。.context("failed to [verb] [noun]") - 库代码:使用定义错误枚举。应用代码:使用
thiserror。anyhow - 当函数有时需要借用、有时需要分配内存时,使用(转换7)。
Cow<str> - 任何返回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 -- a held
.awaitacross an await blocks the executor.MutexGuard - Close stdin explicitly in impls. Brief wait, then fallback kill.
Drop - Use for child processes that must not outlive their parent.
kill_on_drop(true) - Order cleanup deterministically: clear listeners -> drain tasks -> drop resources.
- 在任何之前释放锁守卫(lock guard)——在await期间持有
.await会阻塞执行器。MutexGuard - 在实现中显式关闭标准输入(stdin)。短暂等待后,再强制终止。
Drop - 对于不能比父进程存活更久的子进程,使用。
kill_on_drop(true) - 确定性地排序清理步骤:清除监听器 -> 排空任务 -> 释放资源。
Defensive Serialization
防御式序列化
- not
BEGIN IMMEDIATEfor SQLite write transactions (preventsBEGIN).SQLITE_BUSY_SNAPSHOT - Serialize lock acquisition rather than adding retries.
- over read-then-write for idempotent inserts.
ON CONFLICT DO NOTHING
- 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:
- Can you pass a reference? Use . Zero cost, zero complexity.
&T - Might it be owned or borrowed? Use . Zero-cost when borrowed.
Cow<'_, str> - Multiple owners across sync boundaries? Use . Prefer
Arc<T>overArc::clone(&x).x.clone() - Multiple owners AND mutability? Use or
Arc<Mutex<T>>. ChooseArc<RwLock<T>>when reads vastly outnumber writes.RwLock - Fire-and-forget spawn? Use closure with owned data.
move
Every is a decision point. Ask: "Is this clone necessary, or can I restructure to borrow?"
.clone()当需要共享数据访问时,按以下顺序决策:
- 能否传递引用? 使用。零开销,零复杂度。
&T - 可能是所有权或借用? 使用。借用时零开销。
Cow<'_, str> - 跨同步边界的多个所有者? 使用。优先使用
Arc<T>而非Arc::clone(&x)。x.clone() - 多个所有者且需要可变性? 使用或
Arc<Mutex<T>>。当读取次数远多于写入时,选择Arc<RwLock<T>>。RwLock - 即发即弃(fire-and-forget)的任务? 使用闭包获取所有权数据。
move
每一个都是一个决策点。自问:“这个clone是必要的吗?还是可以重构为借用?”
.clone()Error Philosophy
错误处理哲学
| Context | Tool | Why |
|---|---|---|
| Application code (main, CLI, tests) | | Rich error chains, no boilerplate |
| Library code (crates consumed by others) | | Typed, matchable, callers can branch on variants |
Error enum design: user-facing messages tell the user what to do, not what went wrong internally. large payloads. Classify every variant explicitly in -- no wildcards.
Box<T>is_retryable()| 场景 | 工具 | 原因 |
|---|---|---|
| 应用代码(main、CLI、测试) | | 丰富的错误链,无需样板代码 |
| 库代码(供其他项目使用的crates) | | 类型化、可匹配,调用者可根据变体分支处理 |
错误枚举设计:面向用户的消息要告诉用户该做什么,而非内部发生了什么错误。对大型负载使用。在中显式分类每个变体——不要使用通配符。
Box<T>is_retryable()Async Decisions
异步决策
When to : When thin async wrappers inline large callee futures into their state machine, causing stack pressure. Wrap the inner call: .
Box::pinBox::pin(self.inner_method(args)).await| Need | Channel | Why |
|---|---|---|
| One response back | | Exactly one value, then done |
| Stream of events | | Multiple producers, single consumer |
| Latest value only | | Receivers always see the most recent value |
| Broadcast to all | | Every receiver gets every message |
Shutdown: use for hierarchical shutdown. Never rely on for ordered async cleanup. Never hold a across an point.
CancellationTokenDropMutexGuard.await何时使用: 当轻量异步包装器将大型被调用方future内联到其状态机中,导致栈压力时。包装内部调用:。
Box::pinBox::pin(self.inner_method(args)).await| 需求 | 通道 | 原因 |
|---|---|---|
| 返回单个响应 | | 仅传递一个值,之后结束 |
| 事件流 | | 多生产者,单消费者 |
| 仅需最新值 | | 接收者始终看到最新值 |
| 广播到所有接收者 | | 每个接收者都会收到所有消息 |
关闭:使用进行分层关闭。绝不要依赖进行有序异步清理。绝不要在点持有。
CancellationTokenDrop.awaitMutexGuardPass 3: Simplify
阶段3:简化代码
Review your code as if you're trying to REMOVE things, not add them.
Three diagnostic questions:
- Is any parameter just forwarded through a call chain? Remove it, use ambient access.
- Is any field or function unused? Delete it.
- 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.
像要删除代码一样评审你的代码,而不是添加代码。
三个诊断问题:
- 是否有参数只是在调用链中转发? 删除它,使用环境访问。
- 是否有未使用的字段或函数? 删除它。
- 是否有不必要的抽象?(单次使用的辅助函数、无任何附加功能的包装器)将其内联。
“重写,而非重连”原则: 当函数内部逻辑存在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 explicitlyFix 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
.context()?1. 每个?
操作符搭配.context()
?.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: . The upstream error describes itself -- your job is to name the operation that broke.
"failed to [verb] [noun]"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)NetworkMode::Restricted, AccessLevel::ReadOnly/*param_name*/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/*param_name*/3. BTreeMap for Serialized or Compared Data
3. 序列化或比较数据使用BTreeMap
BEFORE: -- AFTER:
HashMap<String, Rule>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: -- AFTER:
HashMap<String, Rule>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: -- caller writes
AFTER: -- caller writes ,
fn build_command() -> (Vec<String>, Vec<OwnedFd>)result.0fn build_command() -> CommandOutputoutput.argsoutput.preserved_fdsNamed fields are self-documenting. Define a struct for any function returning 2+ values.
BEFORE: -- 调用者写
AFTER: -- 调用者写,
fn build_command() -> (Vec<String>, Vec<OwnedFd>)result.0fn build_command() -> CommandOutputoutput.argsoutput.preserved_fds命名字段是自文档化的。为任何返回2个及以上值的函数定义结构体。
6. Default
Derive on Config Structs
Default6. 配置结构体派生Default
Defaultrust
#[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 so callers override only what matters. For non-zero defaults, implement manually.
DefaultDefaultrust
#[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() };派生以便调用者仅覆盖需要修改的部分。对于非零默认值,手动实现。
DefaultDefault7. Cow<str>
for Conditional Ownership
Cow<str>7. 条件所有权使用Cow<str>
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>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 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+ fields with / guards, it should be an enum.
Option<T>Option<T>is_some()is_none()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> },
}当结构体包含仅在特定组合下有效的字段时,它在类型层面允许无效状态。转换为枚举,每个变体仅携带其相关数据。诊断标志:如果看到2个及以上带有/守卫的字段,就应该改为枚举。
Option<T>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个及以上时,调用站点会变得难以阅读且容易出错。参数结构体在调用站点为每个字段命名,且未来添加参数不会导致破坏性变更。