我随手测了一个真实项目:文件搜索工具 fd。它的工作区代码是 8,273 行——正常 Rust 项目水平。

但 rust-analyzer 为了服务这 8 千行代码,索引了 2,526,193 行依赖代码,占用 1.4GB 内存,冷启动花了 17.4 秒

你写代码的时候,编辑器在背后啃的从来不是你写的代码,而是整个依赖世界。

这就是 rust-analyzer 越用越卡的根源:它的默认行为是「勤快」——把一切能算的都提前算好;而你的效率诉求是「懒」——只算我眼下要用的。 这两者之间的矛盾,决定了你的 IDE 体验。

〇、先给结论:先测量,再决定关什么

不要把所有性能开关一次性关闭。更稳妥的做法是按工作区规模逐档收窄:

工作区情况第一选择主要代价
单 crate 或少量 crate关闭 cachePriming,把 check.workspace 设为 false共享库改动不会立即刷新所有上层错误
十几个到上百个 crate先做第一档,再评估 cargo-subspace未加载 crate 的引用搜索不完整
build.rs/过程宏本身很重只针对性关闭 build scripts 或过程宏宏展开、生成代码和部分配置分析会退化

性能优化的目标不是让 rust-analyzer 变“笨”,而是让它在当前任务上少做无关工作。 每改一档,都要用同一项目、同一工具版本和相近的冷启动条件重新测量。

一、rust-analyzer 的「勤快」三件套

rust-analyzer 不是只会偷懒的补全器。为了让补全、跳转、重命名「快」,它把功夫下在了启动和保存的瞬间。代价是三个默认开启的「勤快」行为:

1. 启动预热全部依赖(cachePriming)

默认 rust-analyzer.cachePriming.enable = true。每次打开项目,它都会把工作区所有 crate 的依赖图、类型信息全部过一遍,把「将来可能用到」的查询结果提前算好缓存起来。

好处是之后补全几乎零延迟;代价是打开项目的头十几秒、以及几百 MB 到 1GB+ 的内存,全部花在「可能用得上」上。

2. 保存时检查整个工作区(check.workspace)

默认 rust-analyzer.check.workspace = true。每次 Ctrl+S,它调用 cargo check 的不是当前 crate,而是整个 workspace 的所有 crate。

3. 启动时索引整个依赖图(eager indexing)

rust-analyzer 从 cargo metadata 拿到整个依赖图后,会急切地加载所有 crate 的 item tree。工作区里 26 个 crate、每个 crate 背后几十上百个依赖,一个都跑不掉。

这三件事单独看都是合理的工程决策:缓存让补全快、全量检查让错误全、急切索引让跳转完整。但它们叠加在一起,就变成了「为你可能永远不会碰的 99% 代码,支付 100% 的时间与内存」。

二、实测:勤快 vs 懒,差距有多大

我在本地做了两组实测(当时使用 rust-analyzer v0.3.2997,记录日期 2026-08-03,Windows + MSVC):

第一组:合成 26-crate workspace(3,217 行代码,链式依赖)

用官方 analysis-stats 命令跑全量类型检查:

方式检查耗时内存墙钟时间
全量 26 个 crate2.36s521MB9.8s
只查单个 crate2.08s523MB2.7s

计算量其实差不多(依赖是共享的),但墙钟时间差了 3.6 倍——多出来的 7 秒,全花在启动时发现项目结构、加载所有 crate 的 item tree 上。

rust-analyzer 索引依赖世界与项目自身代码的规模对比

第二组:真实项目 fd

指标数值
工作区自身代码8,273 行
依赖代码2,526,193 行(305 倍)
类型检查 Total6.08s
峰值内存1,409MB
墙钟时间17.4s

看清楚这个比例:你写的代码只占 0.3%,依赖分析占据了这次测量中的绝大部分时间和内存。 这就是为什么项目一用上 serde、tokio、clap 这类重型依赖,IDE 立刻变慢——不是你的代码变复杂了,是依赖世界变大了。

2.1 先把测量口径说清楚

这里有三条边界必须先说清楚:

  • analysis-stats 是 rust-analyzer 的批处理分析工具,不等同于 VS Code 中完整的 LSP 交互延迟。
  • Total 和峰值内存是这次本地运行的检查器指标,不是 rust-analyzer 对所有项目的固定消耗。
  • 合成 workspace 的“全量 crate”和“单 crate”对比,主要反映项目发现与 item tree 加载开销,不能直接换算成每个真实 monorepo 都能获得 3.6 倍提升。

这组数据适合证明“依赖规模会成为成本”,不适合当作你的机器一定能达到的性能承诺。

三、第一档提速:三个配置关掉「过度勤快」

对中小项目,三行配置就能明显改善启动和保存体验。在 VS Code 的 settings.json 里:

{
  "rust-analyzer.cachePriming.enable": false,
  "rust-analyzer.check.workspace": false,
  "rust-analyzer.checkOnSave": true
}
  • cachePriming.enable = false:取消启动时的全量预热。代价是打开文件后第一次补全会稍慢(只慢一次,按需计算),换来的是启动时间大幅缩短、内存回落。
  • check.workspace = false:保存时只 cargo check 当前 crate 及其依赖,不再检查整个 workspace。代价是「改动一个共享库,其他 crate 的错误不会立即刷新」——但这个信息本来就是异步的,大多数情况下你不需要它。
  • checkOnSave 保持 true:按需检查依然开着,只是把范围从「全部」收窄到「当前」。

在我的 fd 测量中,仅这一档就把冷启动时间从 17.4s 降到了 10s 以内;省下的主要是索引依赖树的时间。这个结果依赖工具版本、缓存和机器配置,不能直接当作普遍保证。

适用边界: 单 crate 项目、小 workspace(<10 个 crate)直接开。如果你的项目大到需要「改一个底层 crate,立刻看到所有上层错误」,再开回 check.workspace = true,或者用下面的进阶方案。

四、第二档:cargo-subspace——真正的「懒加载」

cachePriming 关掉只是省了预热,索引仍可能覆盖整个 workspace。对几百个 crate 的 monorepo,还有一个更激进的方案:cargo-subspace。

它的思路很直接:编辑器打开哪个文件,就告诉 rust-analyzer 当前文件所属 crate 及其依赖。 没有加载的 crate 不会进入当前项目模型。

它绕开 Cargo workspace 的一次性发现,改用 rust-analyzer 的 workspace.discoverConfigrust-project.json 机制:先通过 cargo metadata 得到依赖图,再按“当前文件所属 crate + 它的依赖”裁剪项目模型。cargo-subspace 的 README 给出的 VS Code 配置如下:

rustup component add rust-src
cargo install --locked cargo-subspace

然后在 VS Code 的 settings.json 中配置:

{
  "rust-analyzer.workspace.discoverConfig": {
    "command": ["cargo-subspace", "discover", "{arg}"],
    "progressLabel": "cargo-subspace",
    "filesToWatch": ["Cargo.toml"]
  },
  "rust-analyzer.check.invocationStrategy": "once",
  "rust-analyzer.check.overrideCommand": [
    "cargo-subspace",
    "check",
    "$saved_file"
  ]
}

保存配置后运行 rust-analyzer: Reload Workspace。如果项目模型没有更新,再执行 rust-analyzer: Restart Server

rust-analyzer 三档提速方案:关闭预热、收窄检查范围、按需索引

代价是什么? 懒加载意味着当前 crate 的依赖者信息可能缺失:Find References、跨 crate rename,以及对未加载 crate 的 symbol search 都可能不完整。cargo-subspace README 还明确提示:项目目前没有经过 Windows 测试。本文的 Windows 实测只覆盖 rust-analyzer 本身,不代表 cargo-subspace 在 Windows 上已经验证通过;更稳妥的方式是先在 WSL/Linux 验证,或把 Windows 结果当作待确认项。

这恰好验证了我们的元问题:rust-analyzer 默认的「勤快」是为了让你在任意位置都能立即跳转;而当你只在一小块代码里工作,这种全知全能就是纯浪费。懒加载把「全知」换成了「够用」,把时间还给了你。

五、第三档:只在必要时关闭 build scripts 和过程宏

如果真正拖慢工作区的是 build.rs 或过程宏,继续缩小索引范围还不够,可以做一次更有副作用的实验。官方配置文档中,这两个能力默认都是开启的:

{
  "rust-analyzer.cargo.buildScripts.enable": false,
  "rust-analyzer.procMacro.enable": false
}

这不是通用推荐。关闭后,依赖 build.rs 生成的配置、过程宏展开结果和部分 derive 相关分析可能消失;使用 serdetokiosqlx 等大量依赖宏的项目尤其容易感觉到差异。只有当日志或启动分析明确指向 build scripts/过程宏时,才值得把它作为第三档实验。

验证方法也很简单:保留一份原始设置,分别重启 rust-analyzer,检查三个日常动作——补全、跳转到定义、保存后的诊断——是否仍满足工作需要。如果少了宏展开导致误报或大量“找不到类型”,就恢复默认值。

六、2026 上半年最值得打开的 8 个更新

光调配置还不够。rust-analyzer 每周围绕「更少内存、更快诊断、更少噪音」持续迭代,很多改进会在升级后自动获得,但具体行为仍应以对应版本的 changelog 为准。从 #304 到 #339 的 36 期记录里,我筛出了对日常开发影响最大的 8 项:

1. 升级即省内存:GC 替代 intern 缓存(#307)

rust-analyzer 自己就是 Rust 写的。官方 changelog 报告:把 trait solver 的类型对象从手动 intern 改成 GC 管理后,在 rust-analyzer 自己的项目上省下 648MB 内存和 31 秒启动时间;随后停止展开内置 derive(#308),又报告节省约 180MB;Windows 版本后来换用 mimalloc 分配器(#331)。这些是上游项目的结果,不是本文 fd 实测。

这些改进分布在 2025 年底到 2026 年初的版本中。所以第一件事仍然是把 rust-analyzer 更新到当前稳定版本。 检查方法:命令面板执行 rust-analyzer: Show RA Version,再与官方 release/changelog 对照,不要只凭“版本日期超过一周”判断是否过旧。

2. trait 错误诊断(#326)——Rust 最劝退的报错开始说人话

the trait bound X is not satisfied 是 Rust 新手(和老手)最头疼的报错:它只告诉你「不满足」,不告诉你「为什么」。2026 年上半年新增的 trait 诊断记录并展示义务链:从目标 trait 一路追溯到是哪个 impl 缺了、哪个关联类型没对上。这是 #326「diagnose trait errors」的延续(#338 进一步显示完整义务链)。

3. 新诊断 + 一键修复全家桶

2026 上半年新增了二十多个诊断,全部自带 quick fix:

  • type_mismatch.await(#334):FutureT 用时,直接提示加 .await
  • 数组长度不匹配(#336):[u8; 3] 赋给 [u8; 4],一键生成补全
  • cannot-index-into(#329):Vec<T> 不能按 String 索引时的明确诊断
  • type-must-be-knownunused-must-usemismatched-array-pat-len(#326-329)等一批新诊断

这些诊断的意义不是「报错更多了」,而是「错误更快说人话」——你在错误列表里直接看到修复方案,而不是点开编译器文档猜。

4. 引用搜索排除依赖和标准库(#324)

找引用(Find References)默认可能会搜到依赖和 std,结果列表容易被 serde 等库的内部实现淹没。新版本提供了排除依赖和标准库的选项,可以优先查看自己代码里的引用——噪音会明显减少,但不代表所有外部结果都自动消失。

5. VS Code 里直接建 Rust 项目(#333)

rust-analyzer: Create Rust project 命令:选目录、起名字,直接生成 cargo 项目并在新窗口打开。不用再开终端敲 cargo new

6. 链式表达式折叠 + 类型提示位置(#322)

超长的链式调用(.map().filter().collect())现在可以折叠成一行;类型提示(inlay hints)支持放到行尾而不是变量名旁边,视觉噪音更低。

7. 调试体验:Evaluate Predicate + hover 隐私(#331, #337)

  • Evaluate Predicate 命令:调试时直接评估 trait 约束表达式(配合调试器使用)。
  • hover 时按上下文隐藏私有字段:在外部 crate 里 hover 一个结构体,不再把 pub(crate) 字段全抖出来。

8. 细粒度请求取消(#314)

以前卡住的操作只能干等;现在请求级别的取消让 ESC 能立即中断正在执行的补全/跳转计算,卡顿不再是「按什么都没反应」

六、总结:让 rust-analyzer 回到「工具」的位置

问题默认行为(勤快)推荐配置(懒)适用场景
启动预热cachePriming 全量关闭所有项目
保存检查范围整个 workspace仅当前 crate<10 crate 项目
索引范围全部 cratecargo-subspace 按需monorepo / 大 workspace
构建脚本与过程宏默认开启仅在证据充分时关闭build.rs/过程宏成为瓶颈
版本不定期更新每周更新所有人

三条原则,可以迁移到任何「IDE 变慢」的场景:

  1. 工具默认是为「全能」设计的,不是为「效率」设计的。 打开设置,看看哪些默认开启的全局行为是你根本用不上的,关掉它。
  2. 升级本身是最便宜的优化。 上游已经报告过数百 MB 级的内存改善,但那是特定项目的结果;你仍应在自己的工作区测量。
  3. 让错误说人话,比让错误变少更重要。 诊断信息直接影响你每天被打断的次数——这也是 rust-analyzer 把最大精力投入诊断质量的原因。

你的 Rust 项目只有几千行代码,rust-analyzer 却啃了整个依赖世界。效率不是把工具用得更狠,而是让它只干你要它干的活。

如果你也在被 IDE 卡顿折磨,按“单次只改一档”的顺序实验,并记录冷启动、保存诊断和峰值内存三个指标。

FAQ

Q:关闭 cachePriming 会让补全永久变慢吗?
A:不会。关闭后,第一次访问某个查询时才按需计算,后续结果仍会缓存。换来的通常是更短的启动时间和更低的峰值内存。

Q:什么时候应该保留 rust-analyzer.check.workspace = true
A:当你经常修改共享库,并且需要保存时立刻看到所有上层 crate 的错误时保留它。单 crate 或小 workspace 通常可以先关闭。

Q:cargo-subspace 会影响 Find References 吗?
A:会。未加载的下游 crate 不会出现在引用搜索中,因此它更适合一次只处理一个 crate 的大型 monorepo。

Q:这篇文章里的内存和耗时可以直接复现吗?
A:数据来自文中注明的 fd 项目和合成 workspace;机器、工具链版本、缓存状态都会影响结果。应使用相同版本和冷启动条件重新测量,不要把数字当作所有项目的保证值。

延伸阅读

参考资料