关于 rust-api-design
面向 Rust 编码 Agent 的 crate API 设计技能,指导管理对下游调用方的兼容承诺。内容涵盖公开接口、trait、泛型、依赖暴露及可能破坏兼容性的变更。
主要功能
- 01规划公开 API 接口
- 02比较具体类型、泛型和 `dyn`
- 03指导 trait 与 sealed trait 设计
- 04列出破坏 semver 兼容的变更
- 05提供文档和兼容性检查
适用场景
- 01审查 Rust crate 的导出 API
- 02设计可扩展的 trait 和依赖
- 03发布前评估兼容性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rewrite-rs/ skills --skill rust-api-design安装后在 Claude Code 中输入/rust-api-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
DEPENDENCY-INJECTION.mdSEMVER.mdSURFACE.mdagents/openai.yaml详细介绍
Rust API 设计
crate 根中的所有 pub 项都是对每个下游调用方的承诺。本技能规定调用方能看到并依赖什么——导出的表面、trait 和泛型的形状,以及哪些更改会违背承诺。
表面即契约
默认设为私有,并有意导出:某个类型即使只是因为 pub fn 返回它才设为 pub,它仍属于 API 的一部分,impl 和公有字段也包括在内。养成使用 pub(crate) 的习惯;从 lib.rs 重新导出表面,并将模块路径设为私有:
// lib.rs
pub use crate::internal::parser::Parser; // the surface
mod internal; // the tree stays private
Parser 只能通过一个路径访问:重命名模块、移动文件、拆分 crate——这些都不会构成破坏性更改,因为它们都不可见。
表面拒绝三种形式:一个项在两个路径下均为公有;公有签名中包含 Arc、Rc、Box 或 RefCell;以及签名中包含依赖项类型,从而使该依赖成为 semver 契约的一部分。详细内容见 SURFACE.md。
引入依赖
阶梯从最低一级开始:具体类型——传入该对象;包装结构体——无需改动签名即可替换内部实现;泛型参数——静态分发,但会影响每个持有该参数的类型;dyn Trait——让这种影响止步,但代价是要求满足对象安全。只有当前一级无法表达需求时才向上攀升,绝不要仅为测试需要而这样做——ADR 0006 属于 /rust-testing。各级的代价见 DEPENDENCY-INJECTION.md。
泛型还是 dyn
| 需求 | 选择 | 代价 |
|---|---|---|
| 单态化带来的速度、内联、无需虚函数表 | impl Trait / <T: Trait> | 代码膨胀;每个调用点都会实例化 |
| 异构集合、插件注册表、更小的二进制文件 | dyn Trait | 虚函数表分发;trait 必须满足对象安全 |
| 参数位置、方便调用方使用 | impl Trait 参数 | 调用方无法使用涡轮鱼 |
| 公有 API 中的返回位置 | 命名类型或 impl Trait | impl Trait 会隐藏类型——这是有意的选择,而非捷径 |
对象安全通常足以决定选择:泛型方法、按值返回 Self 的方法,或通过对象使用的关联常量,都排除了 dyn;异构注册表若没有枚举或 trait 对象,就无法使用泛型。
Trait 设计
trait 是调用方用来编写泛型代码的共享行为,不是用于归类相关函数的命名空间。尽量减少必需方法,其余方法提供默认实现——每个必需方法都是对未来每个实现者的承诺。对于每个实现者都只有一个合理选项的情形,使用关联类型(Iterator::Item);对于有多个选项的情形,使用泛型参数(From<T>)。注意 blanket impl——impl<T: Foo> Bar for T 会堵住后续为不满足 Foo 的 SomeType 编写 impl Bar 的路;这个陷阱会在编写第二个 impl 时触发,而不是编写 blanket impl 时。
封闭 Trait
封闭 trait 的作用是让 crate 能够添加方法,而不破坏下游实现者:其超 trait 位于私有模块中,因此只有当前 crate 能实现它,但下游代码仍可在约束中提及该 trait。读者凭记忆复现这种模式时容易出错,因此模式如下:
struct Widget;
mod sealed {
pub trait Sealed {}
}
pub trait WidgetBuilder: sealed::Sealed {
fn new() -> Self;
}
impl sealed::Sealed for Widget {}
impl WidgetBuilder for Widget {
fn new() -> Self {
Widget
}
}
哪些更改会造成破坏,哪些不会
主要情况:为公有枚举添加变体会破坏穷尽匹配(除非该枚举标记了 #[non_exhaustive]);添加必需的 trait 方法会破坏实现者,而添加带默认实现的方法则不会;为未标记 #[non_exhaustive] 的公有结构体添加字段会破坏字面量构造;缩窄参数类型会破坏调用方;扩大返回类型会破坏依赖旧类型编写代码的调用方。完整表格(包括不那么显而易见的情况)见 SEMVER.md。
暂不讨论
此 API 返回的错误类型的形状见 /rust-errors。参数类型是否应为携带不变量的新类型,见 /type-driven-design。公有函数是否应为 async,以及这会对 crate 作出什么承诺,见 /async-rust。pub unsafe fn 是否合理,以及如何记录其契约,见 /unsafe-rust。
验证
cargo doc --no-deps # every public item documented and the links resolve
cargo clippy --all-targets # add -- -D warnings only if the repo has no lint config
cargo test
按照仓库中配置的 lint 级别运行 clippy——不要为了让接口检查通过而强制启用 missing_docs 或其他 lint;应改为提出建议。当 crate 已发布或有下游使用者时,Agent 运行并查看 semver 检查:
cargo semver-checks check-release # requires cargo-semver-checks; skip with a note if unavailable
不要因此在用户环境中安装工具。如果没有 cargo-semver-checks,请说明这一点,并改为参照 SEMVER.md 表格。