如何废弃 API?
解读
在国内 Rust 岗位面试中,面试官提出“如何废弃 API”并不是想听你背文档,而是考察三点:对 Rust 稳定性承诺(Stability Promise)的理解、对 Cargo/SemVer 工作流的熟悉度、对团队协作与下游用户体验的敏感度。答得太浅(只说“打个注释”)会被认为没做过生产级库;答得太深(直接聊 RFC 流程)又容易跑题。最佳策略是:用标准库和主流 crates.io 生态的真实做法做骨架,再补充国内企业内部私有仓库的落地经验,让答案既合规又接地气。
知识点
- 语义化版本(SemVer):rust 生态强制遵循 SemVer,废弃动作必须落在 “小版本(minor)” 里,保证 0.x 或 1.x 现有代码继续编译。
- #[deprecated] 属性:
- 语法:
#[deprecated(since = "1.2.3", note = "请用 xxx 替代")] - 支持函数、结构体、枚举、trait、宏、甚至整条模块。
- 默认告警等级为
allow,需显式#[warn(deprecations)]或deny才能阻断 CI。
- 语法:
- cargo 工作流:
cargo build --all-targets会拉取最新 deprecation warning;cargo clippy -- -D deprecated可把告警变错误,防止国内团队“warning 太多没人看”。
- 文档废弃:
- 在 lib.rs 顶部加
#![doc(issue_tracker_base_url = "https://jira.xxx.cn")],方便把 deprecated 项统一指向内部工单; - 用
#[doc(hidden)]隐藏实现细节,但仍保留导出,保证老二进制链接得上。
- 在 lib.rs 顶部加
- 私有 registry 的“灰度下架”:
- 国内很多企业用 nexus / verdaccio 搭建私有 crates 代理;
- 废弃后先 yank(
cargo yank --vers 1.2.3),阻止新项目依赖,但老项目 lock 文件继续可用; - 等内部所有仓库 CI 通过新版后,再物理删除
.crate文件,完成“软下架→硬下架”。
- 沟通机制:
- 在 CHANGELOG.zh.md 里用“⚠️ 废弃公告”二级标题,写清迁移脚本;
- 飞书/企业微信群里发“废弃巡检机器人”,每天统计仍未整改的仓库,把技术债变成 OKR。
答案
分四步走,全部在 minor 版本完成,绝不 break 现有用户:
- 加属性发告警:在即将废弃的 API 上统一加
#[deprecated(since = "x.y.z", note = "迁移指南见 https://wiki.xxx.cn/rust-migration")];同时把clippy::deprecated提升为deny,让国内 CI 第一时间爆红,防止“warning 淹没”。 - 发 minor 版本并 yank:执行
cargo publish后立刻cargo yank --vers x.y.z,使 crates.io/私有 registry 不再向“新项目”解析该版本,但老 lock 文件继续可用,符合国内金融场景“热补丁”需求。 - 文档与自动化:
- 在 lib.rs 顶部加
#![cfg_attr(feature = "deprecated-cleanup", deny(deprecations))],提供一个 feature 闸口,方便业务线批量整改后一次性开启 deny; - 用
rustdoc --document-private-items生成私有文档,把 deprecated 项集中列在“废弃页”,降低新人踩坑率。
- 在 lib.rs 顶部加
- 制定清除节奏:
- 在内部 confluence 建立“废弃 API 生命周期看板”,列清每个 deprecated 项的“提出时间、迁移责任人、预计删除版本”;
- 至少经历 一个 major 版本周期(如 1.x → 2.x) 后,在下一个 major 里彻底删除代码,同时更新 README:“如仍需旧 API,请锁版本 1.x 并自行维护分支”。
一句话总结:“先告警、再下架、后删除,全程 SemVer 留痕,让编译器帮你背锅。”
拓展思考
- 异步生态的废弃陷阱:
tokio曾经废弃tokio::spawn(async {})的某些旧签名,却因下游大量 proc-macro 依赖而不得不回滚。结论——对外暴露的宏里隐藏 deprecated 项会指数级放大迁移成本,国内团队在设计 crate 边界时应尽量“宏最小化”。 - FFI 废弃:如果 API 已通过
#[no_mangle]导出给 C++/Go,deprecated 属性不会穿透到动态链接层。此时要在.h文件里加__attribute__((deprecated)),并同步发 major 版本才能删除符号,否则会出现国产麒麟系统“升完 so 直接 coredump”的悲剧。 - 灰度编译:大型国企代码量超千万行,可写 cargo plugin 做“条件编译废弃”——
让各业务线先在灰度环境打开 feature 验证,再全量切换,把“编译通过即正确”升级为“编译通过即灰度正确”。#[cfg_attr(feature = "deprecated-allow", deprecated)]