Rust: rustaceanvim al máximo
El mejor setup de Rust en Neovim 0.12: rustaceanvim (nunca rust_analyzer suelto), runnables, testables, debuggables, expand macro, crates.nvim para Cargo.toml y depuración integrada.
Rust en Neovim no se configura con el LSP genérico: se usa rustaceanvim, que envuelve rust-analyzer y le añade todo lo que hace especial a Rust — ejecutar tests inline, expandir macros, explicar un error del compilador, depurar con un comando.
- Por qué rustaceanvim y no una ficha
lsp/rust_analyzer.luatuya. - Runnables, testables, debuggables y expand macro.
- Ajustar rust-analyzer con opciones que existen de verdad.
- crates.nvim para las dependencias de
Cargo.toml.
rustaceanvim, no rust_analyzer
En la lección 2.5 viste el camino normal para cualquier servidor: una ficha en lsp/, un vim.lsp.enable y listo. Rust es la excepción deliberada del track, porque rust-analyzer ofrece un montón de peticiones fuera del protocolo estándar (runnables, expansión de macros, explicación de errores, árbol sintáctico) que un cliente LSP genérico no sabe pedir. rustaceanvim las implementa todas.
Con rustaceanvim instalado, no escribas una ficha lsp/rust_analyzer.lua ni llames a vim.lsp.enable("rust_analyzer"), y tampoco actives esa entrada del catálogo de nvim-lspconfig: el propio plugin configura y arranca el cliente, y montar el servidor a mano en paralelo provoca conflictos. Sus autores también desaconsejan expresamente instalar rust-analyzer con Mason: si la versión del binario no cuadra con la de tu toolchain aparecen fallos sutiles. Instálalo como componente de rustup y olvídate. (Y sí, el viejo rust-tools.nvim está archivado; rustaceanvim es su sucesor.)
rustaceanvim pide Neovim 0.12 o superior, no lleva setup() y hace su propia carga perezosa por tipo de archivo, así que no debes envolverlo en la del gestor de plugins. Se configura poniendo una tabla en vim.g.rustaceanvim antes de que el plugin se inicialice.
return {
"mrcjkb/rustaceanvim",
version = "^9",
lazy = false,
init = function()
require("config.rust") -- define vim.g.rustaceanvim antes de arrancar
end,
}require("config.rust") -- primero la config global, luego el plugin
vim.pack.add({
{
src = "https://github.com/mrcjkb/rustaceanvim",
version = vim.version.range("^9"),
},
})El plugin implementa lazy-loading de verdad: con lazy.nvim va lazy = false y no le pongas ft ni event. Fija un rango de versión para no comerte un cambio incompatible.
Las opciones del servidor viven en su propio módulo:
vim.g.rustaceanvim = {
server = {
default_settings = {
["rust-analyzer"] = {
cargo = { features = "all" }, -- equivale a --all-features
check = { command = "clippy" }, -- clippy en lugar de cargo check
procMacro = { enable = true },
},
},
},
}
Si copias una configuración antigua verás cargo = { allFeatures = true }. Esa opción desapareció del esquema de rust-analyzer: hoy se escribe cargo = { features = "all" }, y su gemela para desactivar las de serie es cargo = { noDefaultFeatures = true }. Tampoco hace falta poner checkOnSave = true: ya viene activado por defecto. Lo que sí cambia el comportamiento es check.command, que es lo que convierte el chequeo al guardar en un pase de clippy.
Las teclas, en el ftplugin
rustaceanvim expone sus funciones por el comando :RustLsp. Como solo tienen sentido en archivos Rust, el sitio correcto para los atajos es un ftplugin, que Neovim ejecuta al abrir cada buffer del tipo:
local buf = vim.api.nvim_get_current_buf()
local function map(k, fn, desc)
vim.keymap.set("n", k, fn, { buffer = buf, desc = desc })
end
map("<leader>rr", function() vim.cmd.RustLsp("runnables") end, "Rust: runnables")
map("<leader>rt", function() vim.cmd.RustLsp("testables") end, "Rust: testables")
map("<leader>rd", function() vim.cmd.RustLsp("debuggables") end, "Rust: debuggables")
map("<leader>rm", function() vim.cmd.RustLsp("expandMacro") end, "Rust: expandir macro")
map("<leader>re", function() vim.cmd.RustLsp("explainError") end, "Rust: explicar error")
map("<leader>rD", function() vim.cmd.RustLsp("renderDiagnostic") end, "Rust: diagnóstico completo")
map("<leader>rc", function() vim.cmd.RustLsp("openCargo") end, "Rust: abrir Cargo.toml")
map("<leader>ro", function() vim.cmd.RustLsp("openDocs") end, "Rust: docs.rs del símbolo")
map("K", function() vim.cmd.RustLsp({ "hover", "actions" }) end, "Rust: hover con acciones")
Fíjate en lo que no hemos hecho: ni <C-Space>, que es del autocompletado nativo de 0.12, ni <Tab>, que gobierna su menú, ni s ni S, que en esta guia son de flash.nvim (y por eso mini.surround vive en el prefijo gs). Las acciones van en <leader>, los saltos en ] y [. La única tecla nativa que pisamos es K, y solo dentro de buffers Rust: el hover de rustaceanvim es un superconjunto del de vim.lsp.buf.hover, así que el cambio no te quita nada.
Runnables, testables y debuggables
Runnables
<leader>rr lista lo ejecutable en el contexto: main, un test concreto, un ejemplo. Elige y corre, sin salir al terminal. Repite lo último con el bang: :RustLsp! runnables.
Testables
<leader>rt es lo mismo restringido a tests. Con la opción tools.test_executor en background los ejecuta en segundo plano y te muestra los fallos como diagnósticos en el propio archivo.
Debuggables
<leader>rd lanza el mismo target bajo el debugger. rustaceanvim carga solo las configuraciones de nvim-dap cuando el cliente se adjunta, y detecta codelldb si lo tienes instalado.
Expand macro
<leader>rm expande recursivamente la macro bajo el cursor y te enseña el código generado. Oro puro para entender un derive o una macro procedural.
Dos comandos más que casi nadie conoce y que valen la lección entera: <leader>re (explainError) busca el diagnóstico con código de error y te muestra la explicación del índice oficial de errores de Rust, y <leader>rD (renderDiagnostic) te enseña el diagnóstico tal y como lo imprimiría cargo, con el contexto de varios archivos junto. Para pelearte con el borrow checker o con genéricos, esa vista es la que de verdad ayuda.
El parser de Rust no es opcional
nvim-treesitter.configs, ensure_installed, highlight e indent desaparecieron con la rama master. En la rama main, que es la única viva, el plugin solo instala parsers; el resaltado lo enciende Neovim con vim.treesitter.start().
require("nvim-treesitter").install({ "rust", "toml" })
-- Sin foldlevelstart = 99, cada archivo se abre ENTERO PLEGADO.
-- Ponlo una vez en tus opciones (leccion 2.3).
vim.o.foldlevelstart = 99
vim.api.nvim_create_autocmd("FileType", {
pattern = { "rust" },
callback = function()
vim.treesitter.start()
vim.wo[0][0].foldexpr = "v:lua.vim.treesitter.foldexpr()"
vim.wo[0][0].foldmethod = "expr"
end,
})
Aquí el parser no es solo cosmético: el comando :Rustc unpretty, que te enseña la representación intermedia de la función bajo el cursor (necesita además una toolchain nightly), requiere el parser de Rust instalado.
crates.nvim: dependencias en Cargo.toml
return {
"saecki/crates.nvim",
tag = "stable",
event = { "BufRead Cargo.toml" },
config = function()
require("crates").setup()
end,
}vim.pack.add({
{ src = "https://github.com/saecki/crates.nvim" },
})
require("crates").setup()Con el tag stable te quedas en la última release; sin él vas al filo. Los ajustes finos están en la wiki del proyecto.
Con él, Cargo.toml deja de ser un archivo ciego: ves qué versión de cada crate es la última publicada y gestionas las dependencias sin salir del buffer.
Formateo y depuración
rustfmt viene con la toolchain, y ya declaraste rust = { "rustfmt" } en conform (lección 2.4), así que se formatea al guardar respetando tu rustfmt.toml.
Para depurar necesitas nvim-dap (lección 2.4) y un adaptador: codelldb da mejor experiencia que lldb en Rust y rustaceanvim lo detecta solo si está instalado. Ojo al matiz: <leader>rd solo carga las configuraciones que genera rust-analyzer, mientras que require("dap").continue() carga todas las de Rust, incluidas las que tengas en un .vscode/launch.json.
Escribe, guarda (clippy te revisa al instante), <leader>rt para correr el test bajo el cursor, y si falla <leader>rD para leer el error como lo imprime cargo o <leader>re para que te lo expliquen; si aun así no lo ves, <leader>rd y breakpoints. Todo sin tocar el ratón ni cambiar de ventana. Pero hay una lección más general escondida aquí: rustaceanvim existe porque rust-analyzer habla más de lo que el protocolo estándar sabe escuchar. Cuando un servidor te ofrece extensiones propias, la pregunta útil no es “¿qué plugin lo hace bonito?”, sino “¿qué peticiones extra está exponiendo este servidor y quién las traduce a comandos?”. Sabiendo eso puedes leer el README de cualquier plugin de lenguaje y decidir en dos minutos si te aporta algo o solo envuelve lo que tu cliente ya hace.
- Instala rustaceanvim con un rango de versión y comprueba que no tienes ninguna ficha ni
vim.lsp.enablepara rust-analyzer. - Pon
check.commanden clippy y verifica con:checkhealth rustaceanvimque todo está en su sitio. - Abre un proyecto Cargo y, sobre un test, pulsa
<leader>rtpara correrlo. - Rompe un tipo a propósito y compara
<leader>recon<leader>rDsobre el mismo error. - Sobre un
derive, usa<leader>rmpara ver qué código genera. - Instala el parser de Rust y confirma que
vafselecciona una función entera.