Vim Setup For Rust

Sep 16, 2017

One of my favorite things about Rust is the stellar tooling, and it’s led to a Vim setup I’m pretty happy with. A lot of this is similar to my setup for Scala the notable additions are Neomake and Racer.

My flavor of Vim is Neovim and my full init.vim + dotfiles are over on Github. For working in Rust in Vim, I use:

1. Syntax

rust-lang/rust.vim I’m just using the syntax support, but it also has Syntastic and rustfmt support if that’s your thing.

2. Completion

If there’s one thing to have, it’s Racer. Racer provides context sensitive Rust code completion (for your code, standard library and dependencies). I pair this with deoplete out of personal preference.

From what I can tell, deoplete alone completes previously used words. Racer adds completions based on context, available definitions from imports, and dependencies:

Racer vim completion for rust

Completion based on what is imported: flip_normals is defined in another module. Additionally, g:racer_experimental_completer is enabled for arguments and return types

Racer vim completion for rust

Completion for available modules

3. Jump to File

FZF works really well and really fast. I’ll still fall back onto CtrlP when I don’t have the FZF binary installed.

4. Jump to Definition

Racer also provides code navigation via jump to definition and opening docs.

Racer vim jump to definition for rust

Jumping to std::io::Write source

Honestly, even though Racer is superior, I still end up using FZF + ctags because I do the same thing for Scala. If you haven’t heard of ctags, it’s essentially doing a one time regex search for definitions (e.g. functions, traits) to create an index that you can efficiently search through later (e.g. here with FZF). I’ve heard of some ctag plugins for rust like rusty-tags, but I just end up using these rules in my ~/.ctags:

--regex-Rust=/^[ \t]*(#\[[^\]]\][ \t]*)*(pub[ \t]+)?(extern[ \t]+)?("[^"]+"[ \t]+)?(unsafe[ \t]+)?fn[ \t]+([a-zA-Z0-9_]+)/\6/f,functions,function definitions/
--regex-Rust=/^[ \t]*(pub[ \t]+)?type[ \t]+([a-zA-Z0-9_]+)/\2/T,types,type definitions/
--regex-Rust=/^[ \t]*(pub[ \t]+)?enum[ \t]+([a-zA-Z0-9_]+)/\2/g,enum,enumeration names/
--regex-Rust=/^[ \t]*(pub[ \t]+)?struct[ \t]+([a-zA-Z0-9_]+)/\2/s,structure names/
--regex-Rust=/^[ \t]*(pub[ \t]+)?mod[ \t]+([a-zA-Z0-9_]+)/\2/m,modules,module names/
--regex-Rust=/^[ \t]*(pub[ \t]+)?(static|const)[ \t]+(mut[ \t]+)?([a-zA-Z0-9_]+)/\4/c,consts,static constants/
--regex-Rust=/^[ \t]*(pub[ \t]+)?(unsafe[ \t]+)?trait[ \t]+([a-zA-Z0-9_]+)/\3/t,traits,traits/
--regex-Rust=/^[ \t]*(pub[ \t]+)?(unsafe[ \t]+)?impl([ \t\n]*<[^>]*>)?[ \t]+(([a-zA-Z0-9_:]+)[ \t]*(<[^>]*>)?[ \t]+(for)[ \t]+)?([a-zA-Z0-9_]+)/\5 \7 \8/i,impls,trait implementations/
--regex-Rust=/^[ \t]*macro_rules![ \t]+([a-zA-Z0-9_]+)/\1/d,macros,macro definitions/

FZF and ctags for Rust in Vim

Searching for Material with FZF and ctags

5. Compile on save + Jump to Error

For Rust, I’ve been using Neomake which I have compile on every save. Errors and warnings populate the location list, which you can use to jump to error, and a sidebar for visual feedback. I also find vim-togglelist a nice addition for toggling the location list with <leader>l.

Neomake for Rust

Neomake error reporting: sidebar markings and location list on save

However, it wouldn’t be Vim without having some bizarre edge case. When exitting with :wq Neomake kicks off a Cargo check process that holds the lock and never exits, and I would have to manually kill it to be able to compile again. This led me to this fun snippet:

" Neomake
" Gross hack to stop Neomake running when exitting because it creates a zombie cargo check process
" which holds the lock and never exits. But then, if you only have QuitPre, closing one pane will
" disable neomake, so BufEnter reenables when you enter another buffer.
let s:quitting = 0
au QuitPre *.rs let s:quitting = 1
au BufEnter *.rs let s:quitting = 0
au BufWritePost *.rs if ! s:quitting | Neomake | else | echom "Neomake disabled"| endif
let g:neomake_warning_sign = {'text': '?'}


Debugging + Types

Most of the time, I’m getting by with println! for debugging. I rarely need type inspection in Rust, but when I do, I do my typical hack of putting in a bogus type like int and seeing what the compilers gives as an expected and actual type.

Cargo Watch

I’m always working in a tmux session, so I use cargo-watch in separate window (e.g. cargo watch -x check). For check this is redundant with Neomake, but you still may want to scroll through errors or use another command like cargo watch -x test