xynovim guide
Instant Rust diagnostics in Neovim with rust-analyzer and clippy
A Rust file in Neovim is only as responsive as its diagnostics. This guide describes the two-tier setup in xynovim, a personal Neovim configuration: rust-analyzer answers while you type, and clippy follows about half a second later, on every change and before you save. It explains why there are two tiers, what each one reports, how to read the result, and what is deliberately not covered. The timings are my own measurements on a warm session, not a promise for your machine.

Why two tiers
rust-analyzer reports some errors from memory, without running cargo. That tier is fast: type errors such as a mismatched type (E0308) appeared in about 0.01 to 0.15 seconds in my measurements. It is also narrow. Unresolved names, methods, types and imports (E0425, E0599, E0432) are only reported by rust-analyzer with diagnostics.experimental.enable, and I measured that setting on a copy of a real 196-file axum and sqlx workspace: off gave no rust-analyzer diagnostics, on gave 82 false errors. So it stays off, and those errors come from the second tier.
The second tier runs clippy. It is slower, about 0.56 seconds median when warm, but it sees everything cargo sees, plus lints. Together the two give an instant answer for type errors and a deeper answer shortly after.

How the clippy tier works
The depth tier is bacon-ls with its native cargo backend (version 0.31.0 or newer, installed by Mason). It mirrors your workspace into a hardlinked shadow under target/bacon-ls-live/ and re-runs clippy --workspace --all-features --no-deps --keep-going after a 500 ms debounce on every buffer change. Because it works on the shadow, the result reflects what is in the buffer, not only what is on disk.
Two settings matter and both look backwards. checkOnSave is off: with an auto-save of about one second it became a second cargo pipeline racing the live one, and the runs cancelled each other. updateOnInsert is on: without it bacon-ls advertises no document sync and its diagnostics never refresh. The reasoning and the measurements are in docs/DETAILS.md.
Reading the diagnostics
- The virtual text shows the first line of the message plus the source in brackets, for example
[bacon-ls]. Clippy sends about fourteen lines per lint, so the full text is one key away:<leader>cdopens the diagnostic float. - A problem that both tiers report on the same line (same rustc error code, or the same first line) is shown once, preferring rust-analyzer's text.
- The statusline has a rust-analyzer part (
RA ready,RA loading,RA stopped) and a clippy part:clippy running,clippy stalewhile you have typed since the last run,clippy okwhen there are no errors,clippy 2E(the error count) when there are some, andclippy failedwhen cargo cannot run at all, for instance with a broken manifest.

One feature set for both tiers
A crate with mutually exclusive features is where two checkers disagree. :RustContext (also <leader>rF) lets you choose the Cargo features and the build target per project, and applies the choice to rust-analyzer and bacon-ls together. The choice is stored per workspace and survives restarts. In the demo crate, ssr and csr are mutually exclusive: with all features on, the conflict is reported (once, as rust-analyzer's message, with clippy's identical report merged into it), and choosing only ssr makes it go away.

:RustContext.Keeping the servers alive
A language server that dies stays dead in a stock setup, and the editor looks fine while showing stale errors. Here the statusline shows RA stopped or clippy stopped, and the server is restarted automatically after half a second, at most three times a minute, then it gives up with an error message. :RustRestart restarts both by hand. Saving Cargo.toml or Cargo.lock from Neovim restarts bacon-ls 500 ms after the last save, so a stale cannot find crate error does not linger, and the statusline shows deps resolving until rust-analyzer has reloaded. When an open .rs, .toml or .lock file is reloaded from disk after a change made outside Neovim (a git checkout, a script), or the git HEAD of the workspace moves, bacon-ls is restarted on the next focus event so it does not keep checking old content.
In a Cargo workspace, bacon-ls is rooted at the workspace, not at the member crate you happen to open, so there is one client per workspace. It does not start for a loose .rs file without a crate, nor for sources under ~/.cargo/registry or ~/.rustup.
What it does not cover
- Clippy runs without
--all-targets, so test modules,tests/, examples and benches get rust-analyzer's type errors but no live unresolved-name errors.cargo clippy --all-targetsor<leader>trreports them. - The two tiers can briefly show the same type error twice.
rust-project.jsonprojects were not tested.
Try it
Install the configuration, open demo/netwatch or any Cargo project, type a wrong type into a function and watch the statusline. :checkhealth xynovim lists what is missing.
git clone https://github.com/xynorash/xynovim ~/.config/nvim # or ln -s <checkout> ~/.config/nvim
~/.config/nvim/scripts/bootstrap.sh
xynovim is a personal project: the home page has the tour, the install details and the platform status.