mcpField
R
rust-api-design

rust-api-design

github.com/rewrite-rs其他1039 次安装10 周前更新
代码仓库

关于 rust-api-design

面向 Rust 编码 Agent 的 crate API 设计技能,指导管理对下游调用方的兼容承诺。内容涵盖公开接口、trait、泛型、依赖暴露及可能破坏兼容性的变更。

主要功能

  1. 01规划公开 API 接口
  2. 02比较具体类型、泛型和 `dyn`
  3. 03指导 trait 与 sealed trait 设计
  4. 04列出破坏 semver 兼容的变更
  5. 05提供文档和兼容性检查

适用场景

  1. 01审查 Rust crate 的导出 API
  2. 02设计可扩展的 trait 和依赖
  3. 03发布前评估兼容性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rewrite-rs/skills --skill rust-api-design

安装后在 Claude Code 中输入/rust-api-design确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

DEPENDENCY-INJECTION.md
SEMVER.md
SURFACE.md
agents/
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 Traitimpl 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 表格。

评论

支持 Markdown