关于 rust-semver
面向 Rust crate 维护者的发布管理技能,讲解 SemVer 兼容性并检查 API 破坏性变更,也涵盖发布、工作区版本协调、撤回版本和安全公告处理。
主要功能
- 01判断破坏性与兼容性变更
- 02应用 1.0 前版本规则
- 03解读 cargo-semver-checks 结果
- 04协调工作区发布
- 05处理撤回版本与安全公告
适用场景
- 01确定 crate 的版本升级幅度
- 02发布前检查 API 变更
- 03准备多 crate 工作区发布
- 04响应 crate 漏洞公告
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ full-stack-skills/ rust-skills --skill rust-semver安装后在 Claude Code 中输入/rust-semver确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlexamples/workspace-publishing.mdexamples/golden-semver/Cargo.lockCargo.tomlexamples/golden-semver/src/lib.rsreferences/cargo-semver-checks-lints.mdsemver-reference.md详细介绍
Rust Semver 与发布工作流
权威参考:Cargo Semver Reference、RFC 1105 (Semver)、cargo-semver-checks、RustSec Advisory Database。
此技能负责判定 Rust 中什么是破坏性变更,处理 crates.io 的发布工作流,以及协调整个工作区的发布。它不负责 API 设计(rust-api-design)、Cargo 清单机制(rust-cargo-build)或 lint 配置(rust-style-clippy)。
能力边界
✅ 优势
- 判断特定代码变更应提升主版本号、次版本号还是修订版本号
- 理解 1.0 之前(
0.x.y)的特殊规则 - 运行并解读
cargo-semver-checks(270 多项 lint) - 通过
cargo-workspaces协调多 crate 工作区发布 - 处理撤回(yank)、弃用和 RustSec 安全公告工作流
- 了解
#[non_exhaustive]、sealed trait、功能标志、隐藏模块对 semver 的影响 - 编写与变更类型相符的发行说明
⚠️ 前提条件
- 具有明确公共 API 的 Rust crate——参见
rust-api-design - Cargo 清单和
cargo publish基础知识——参见rust-cargo-build
❌ 不在范围内
- 设计 API 本身 →
rust-api-design - Cargo 清单格式和构建配置 →
rust-cargo-build - Clippy lint 规则 →
rust-style-clippy - 用于验证行为的测试设计 →
rust-testing
数据隐私
此技能不会收集、存储或传输用户数据。向 crates.io 发布属于公开操作——运行 cargo publish 前请先征得用户同意。
第 1 部分:什么是公共 API?
Semver 适用于公共 API 表面。在 Rust 中,公共 API 是下游可见的一切,但其定义并不简单。
可见项
// src/lib.rs
pub fn hello() {} // public API
pub struct User { pub name: String } // public API
pub mod network { // public API
pub fn connect() {} // public API
}
mod internal { // private — NOT public API
pub fn helper() {} // pub, but module is private → not reachable
}
规则:当且仅当下游 crate 无需 unsafe 就能命名某项时,该项才属于公共 API。pub(crate) 模块中的 pub 项不属于公共 API。
通过重新导出实现可达
// src/lib.rs
mod hidden;
pub use hidden::Config; // Config IS public API (reachable via crate root)
虽然 hidden 是私有的,但可以通过重新导出访问 Config——因此它属于公共 API。
隐藏模块
#[doc(hidden)]
pub mod unstable { /* */ }
**就 semver 而言,#[doc(hidden)] 不会将项从公共 API 中移除。**下游仍然可以 use crate::unstable::Foo。如果你希望这些项保持不稳定,应通过功能标志进行门控,或将其移至私有模块。
受功能标志控制的项
#[cfg(feature = "json")]
pub mod json; // public API only when feature enabled
Semver 契约针对每种功能组合分别成立。在 json 下添加项,对启用了 json 的用户来说属于次版本变更,对未启用该功能的用户则不可见。
第 2 部分:破坏性变更与非破坏性变更
Cargo Book 列出了规范变更。概要如下:
始终属于破坏性变更(提升主版本号,1.0 之前除外)
| 变更 | 原因 |
|---|---|
| 移除公共项 | 引用了该项的下游代码会出错 |
| 重命名公共项 | 等同于移除旧项并添加新项 |
| 更改函数签名(参数/返回类型) | 调用方将无法通过编译 |
向非 #[non_exhaustive] 枚举添加新变体 | match 缺少通配分支 |
| 向公共结构体字面量添加字段 | 如果 Foo 新增字段,Foo { x: 1 } 将出错 |
| 为外部类型实现 trait(或反过来) | 会与下游的孤儿实现冲突 |
| 收紧 trait 约束 | 下游实现可能不再满足要求 |
将 pub 项改为 pub(crate) | 从 API 中移除 |
移除 Copy/Clone/Debug 派生 | 下游可能依赖这些特性 |
| 更改公共依赖项的 semver 版本 | 可能导致传递性破坏 |
不构成破坏性变更(次版本或补丁版本升级)
| 变更 | 原因 |
|---|---|
| 添加新的公共函数 | 增量式变更 |
| 为你自己的类型添加新的 trait 实现 | 增量式变更 |
为 #[non_exhaustive] 枚举添加一个变体 | 调用方有通配分支 |
| 为私有字段结构体添加一个字段 | 调用方无法使用结构体字面量创建实例 |
| 添加新的功能标志 | 对未使用该功能的用户不可见 |
| 放宽 trait 约束 | 更加宽松 |
| 为封闭 trait 添加默认方法 | 封闭 = 由你控制实现 |
使用 #[deprecated] 弃用 | 渐进式移除 |
容易忽视的情况
为公共 trait 添加方法
// Before
pub trait Foo { fn a(&self); }
// After
pub trait Foo { fn a(&self); fn b(&self); } // ❌ BREAKING
下游对 Foo 的实现会无法通过编译(缺少 b)。解决方法:任选其一:(a)升级主版本;(b)在添加方法前封闭该 trait;或(c)将 b 添加为默认方法(fn b(&self) {})。
为未封闭的 trait 添加默认方法
pub trait Foo { fn a(&self); fn b(&self) {} } // ✅ non-breaking
默认方法不构成破坏性变更。但之后不能移除默认实现,否则会构成破坏性变更。
带默认值的泛型参数
// Before
pub fn parse<T>(input: &str) -> T { /* */ }
// After
pub fn parse<T = String>(input: &str) -> T { /* */ } // ✅ non-breaking
为泛型参数添加默认值不构成破坏性变更。移除默认值则会构成破坏性变更。
trait 对象安全性
如果 Foo 当前满足对象安全(Box<dyn Foo> 可用),那么使其不再满足对象安全(添加 Self: Sized 约束、添加泛型方法)会构成破坏性变更。
添加生命周期
// Before
pub fn parse(input: &str) -> &str { /* */ }
// After
pub fn parse<'a>(input: &'a str) -> &'a str { /* */ } // ✅ usually non-breaking
生命周期省略规则会使大多数生命周期变更不影响调用方。
第 3 部分:1.0 之前(0.x.y)的规则
版本为 0.x 的 crate 遵循经过调整的语义化版本规则:
| 版本 | 兼容范围 | 破坏性变更时递增的部分 |
|---|---|---|
0.0.x | 无——实验阶段,可进行任何变更 | 0.0.x |
0.x.y,其中 x > 0 | 仅限相同的 0.x.* | 次版本号(x)的位置 =“主版本号” |
因此,0.1.0 → 0.2.0 属于主版本升级。0.1.0 → 0.1.1 属于补丁版本升级(修复 bug)。
# Allowed in Cargo.toml:
[dependencies]
my-crate = "0.1" # means >=0.1.0, <0.2.0
达到 1.0.0 后,恢复使用常规语义化版本规则。
何时升级到 1.0
- API 已稳定(未来约 6 个月内没有计划中的破坏性变更)
- 已被其他 crate 使用(表明它有用)
- 所有基础设计决策都已确定
不要害怕发布 1.0。许多 crate 因为害怕承担承诺而在 0.x 阶段停留太久。1.0 的代价是“我承诺,破坏性变更需要升级主版本”,这通常完全没问题。
第 4 部分:cargo-semver-checks
cargo-semver-checks 是一个工具,可将 crate 当前的公共 API 与已发布版本进行比较,并标记破坏性变更。270 多项 lint 覆盖了大多数情况。
安装和运行
cargo install cargo-semver-checks --locked
# Before publishing a new version:
cargo semver-checks check-release
# Compare against a specific baseline:
cargo semver-checks check-release --baseline-version 1.2.0
# Compare against a git tag:
cargo semver-checks check-release --baseline-tag v1.2.0
解读输出
--- failure function_missing ---
Description: Function removed or renamed, or return type changed.
Error: the function `parse` was removed from the API
每个失败都代表违反了语义化版本规则。可通过以下方式修复:
- 恢复该项(不要实际移除它)
- 升级主版本(承认这是破坏性变更)
- 使用兼容性垫片(以旧名称重新添加,并委托给新的实现)
集成到 CI
# .github/workflows/semver.yml
name: Semver Check
on: [pull_request]
jobs:
semver:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: obi1kenobi/cargo-semver-checks-action@v2
引入破坏性变更的 PR 会导致 CI 失败,除非相应地升级版本。
常见的 lint 命中项
| Lint | 含义 | 修复方法 |
|---|---|---|
function_missing | 函数已移除或重命名 | 恢复该函数或升级主版本 |
enum_variant_added | 为非 #[non_exhaustive] 枚举添加了变体 | 标记为 #[non_exhaustive] |
struct_field_added | 为公共结构体添加了字段 | 将字段设为私有或标记为 #[non_exhaustive] |
trait_method_added | 为未封闭的 trait 添加了方法 | 添加默认实现或封闭该 trait |
inherent_method_missing | 移除了实现块中的方法 | 恢复该方法或升级主版本 |
type_marked_deprecated | 添加了 #[deprecated] | 可以接受,升级次版本 |
第 5 部分:发布工作流
发布单个 crate
# 1. Bump version in Cargo.toml
# 2. Update CHANGELOG.md
# 3. Run quality gates
cargo fmt --all --check
cargo test --workspace --all-targets --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo semver-checks check-release
# 4. Verify package contents
cargo package --list # show files included
cargo package # build the .crate in target/package/
cargo publish --dry-run # full simulation
# 5. Publish
cargo publish # 🚨 public action — confirm with user first
通过 cargo-workspaces 同步发布工作区
对于多 crate 工作区,cargo-workspaces 可自动完成版本升级和发布流程:
cargo install cargo-workspaces
# Bump all members together:
cargo workspaces version minor # 0.1.0 → 0.2.0 across all members
# Bump with custom prerelease:
cargo workspaces version prerelease --pre-id beta
# Publish all members in topological order:
cargo workspaces publish
cargo-workspaces:
- 编辑每个成员的
Cargo.toml版本 - 将内部的
path = ""依赖更新到新版本 - 如果传入
--no-individual-tags或--individual-tags,则提交变更并打标签 - 按依赖顺序发布(先发布依赖,再发布依赖方)
撤回已发布的版本
如果某个版本存在严重漏洞,撤回它(阻止新增依赖,不会移除现有依赖):
cargo yank --vers 1.2.3
# To undo:
cargo yank --vers 1.2.3 --undo
对于通过 Cargo.lock 固定该版本的项目,已撤回的版本仍会保留在注册表中。
弃用 crate
通过维护者页面在 crates.io 上将该 crate 标记为已弃用。下游用户运行 cargo add 时会看到弃用通知。
第 6 部分:RustSec 安全公告
RustSec 安全公告数据库 跟踪已发布 crate 中已知的漏洞。
cargo-audit
cargo install cargo-audit --locked
cargo audit # scan Cargo.lock against advisory DB
cargo audit --deny warnings # fail CI on any advisory
cargo-deny(功能更全面)
cargo-deny 涵盖:
- 安全公告 — RustSec
- 许可证 — 拒绝未经授权的许可证
- 禁止项 — 拒绝特定 crate
- 来源 — 拒绝特定的 git/注册表来源
# deny.toml
[advisories]
db-urls = ["https://github.com/rustsec/advisory-db"]
yanked = "deny"
[licenses]
allow = ["MIT", "Apache-2.0", "BSD-3-Clause"]
confidence-threshold = 0.8
cargo install cargo-deny --locked
cargo deny check
响应 crate 中的安全公告
- 在问题跟踪器中确认收到
- 修复问题并发布新版本
- 向 RustSec 提交安全公告(如适用):https://github.com/rustsec/advisory-db/
- 撤回存在漏洞的版本(修复发布后)
- 通过 CHANGELOG 和发行说明告知用户
第 7 部分:发行说明
每次发布时,都要在 CHANGELOG.md 中添加条目。建议格式:
## [1.2.0] - 2026-07-21
### Added
- `Client::builder()` for ergonomic construction (#45)
- `Visibility::IncludeHidden` variant
### Changed
- `parse()` now accepts `impl AsRef<str>` instead of `&str`
### Deprecated
- `Client::new()` — use `Client::builder()` instead
### Fixed
- Panic on empty input in `parse()` — now returns `Err`
工具:
cargo-release— 自动更新版本号、添加标签并发布release-plz— 根据规范化提交生成 CHANGELOG- setuptools-changelog — 手动操作
工作流
- 盘点差异 — 自上次发布以来,
pub项发生了哪些变化? - 对每项变更分类 — 破坏性变更、新增功能或修复(参见第 2 部分)
- 决定版本号调整幅度 — 主版本/次版本/补丁版本;记住 1.0 前的规则
- 运行 cargo-semver-checks — 确认你的分类与工具结果一致
- 更新 CHANGELOG — 说明变更内容、原因以及迁移说明
- 运行质量检查 — fmt、test、clippy、doc
- 发布 — 单个 crate(
cargo publish)或工作区(cargo workspaces publish) - 处理发布后的事项 — 如果有问题则撤回,如果涉及安全问题则提交安全公告
快速决策参考
| 变更 | 版本号调整 | 备注 |
|---|---|---|
新增 pub fn | 次版本 | 新增功能 |
| 修复 bug | 补丁版本 | 行为变更,不涉及 API 变更 |
移除 pub fn | 主版本 | 或恢复后标记为弃用 |
| 新增封闭 trait 方法 | 次版本 | 封闭表示你可以控制其实现 |
| 新增未封闭 trait 方法 | 主版本 | 或添加默认实现 |
| 新增枚举变体 | 主版本(如果标记为 #[non_exhaustive],则为次版本) | |
| 新增 pub 结构体字段 | 主版本(如果标记为 #[non_exhaustive] 或该字段为私有,则为次版本) | |
| 提升 MSRV | 次版本(如果最低版本为 1.0+) | 记录相关策略 |
| 将依赖升级至不兼容的语义化版本主版本 | 主版本 | 传递性破坏变更 |
资源
- Semver 参考(完整版) — 完整的变更目录
- cargo-semver-checks Lint 目录
- 工作区发布指南 —
cargo-workspaces使用示例 examples/golden-semver/— 展示非破坏性版本升级的 crate