Rust, Vim and friends.
Or "old tools" for modern development.
Rust is a systems programming language commonly utilized for tasks such as systems programming, embedded systems, low-level applications, web browser development, and creating low-latency, high-performance distributed applications.
While it may not seem like a language that would attract widespread interest, Rust's popularity has been growing for years now. This trend can be compared to the rise of Java over 30 years ago when it revolutionized the industry by offering a more effective and powerful solution for tackling complex tasks. One of the superpowers of Rust comes from the development environment: the tools and applications supporting the developers are great and constantly improving; plus, they are not so difficult to configure and tailor to some specific needs.
This situation, plus my constant curiosity, sparked a simple question: can I do it differently? Can I use something different than the usual VsCode (great tool, don't get me wrong)? What about creating an environment that is very Rust-oriented, if it even exists that word, trying to use fewer resources, more keyboard-centric, and maybe even using my old beloved tools like terminal and Vim? The answer is yes. Even better, we can use all those "classic" (a polite way of saying "old") tools in a modern way. But first...
Why?
In summary, a modern terminal-based environment offers me speed and resource efficiency, using less memory and CPU. It is keyboard-centric, which enhances my focus and speeds up my workflow by minimizing my reliance on the mouse. Additionally, it reduces my dependency on the latest shiny IDEs or tools, whilst being able to leverage powerful integrations to things like LLM and AI
Plus, it is cheap and green (just a couple of buzzwords ;-P ). I started years ago, and now I always buy refurbished hardware, helping the environment by giving a second chance to perfectly still good working hardware, and saving thousands of bucks, avoiding the marketing trap of needing to buy the latest machine.
It is all Open Source
Let's be clear from the beginning. It is all Open Source, and freely available online.
First of all, it runs on Linux. Nowadays, I mostly use Linux Mint. After years of using/battling with distros like Slackware and then even Arch, or Debian, I switched to a modern, stable and easy distro that still allows me to tweak and change almost everything, but without getting mad whenever I want to integrate a new kernel or a new version is available.
But it doesn't really matter the distro: today, the Linux environment is so incredibly powerful and stable that a distro choice is mostly a case of personal taste.
Window manager: i3.
A bit out of the scope of this post since it is not particularly focused only on development or Rust, but pertinent to the effort of minimizing resource usage and having a more keyboard-focused workflow, i3 (https://i3wm.org/) is a small, simple, yet powerful and feature-rich tilling (not overlapping frames) windows manager. It allows me to easily manage multi-monitor and different modes (similar to Vim) from my keyboard.
Removing some services and using i3 on my old laptop, I achieved a starting memory footprint of less than 300Mb (yes, it is correct MegaBytes!!).
Terminal Emulator: Alacritty
Let's dive into the "rewrite it in Rust" World using the less-expected new tool: a terminal emulator completely rewritten in Rust.
Alacritty (https://alacritty.org/) is a modern terminal emulator that leverages GPU acceleration for rendering, resulting in exceptional performance. It's cross-platform, minimalist by design, and configured entirely through a YAML file.
Installation and Configuration
-
On Linux (Debian/Ubuntu)
sudo apt install alacritty
-
On macOS with Homebrew
brew install alacritty
Or if you have Rust installed and you prefer more control, you have two options:
- install it with cargo:
cargo install alacritty
- install it from the source (please check the documentation):
git clone https://github.com/alacritty/alacritty.git cd alacritty cargo build --release
The beauty of Alacritty is its simplicity - it does one thing (terminal emulation) extremely well while leaving other functionality to dedicated tools that integrate pretty well. On top of that, some unique features like Vi mode, Search, and Multi-Window allow me to do most of the actions from my keyboard and save memory and resources.
Configuring Alacritty could become a full-time job tweaking all the available settings, but sticking to the default options already provides an enjoyable and productive setup. A nice addition is the theme repository (https://github.com/alacritty/alacritty-theme), providing palettes and colour combinations for all tastes. A simple example from my config file using the config docs as a guide:
[general]
## Reload config on changes
live_config_reload = true
## Import themes.
import = ["~/.config/alacritty/themes/tokyo_night_enhanced.toml"]
#[env]
#TERM = "xterm-256color"
[window]
## Set CWD as window title
dynamic_title = true
## Ignored when dynamic_title = true
title = "Alacritty"
## Fullscreen, Maximized, Windowed, SimpleFullscreen
startup_mode = "SimpleFullscreen"
## How transparent the window is; 0.0 to 1.0
opacity = 0.85
## Add additional padding evenly around terminal content
dynamic_padding = true
padding.x = 4
padding.y = 0
...
Terminal Multiplexer: Tmux
Tmux is an evergreen tool; so simple and yet so powerful, especially with small monitors and reduced resources but still dealing with complex projects. This is where tmux shines - it allows you to create multiple terminal sessions within a single window, detach from and reattach to sessions, and maintain your development environment even if your connection drops.
Installation and Basic Configuration
-
On Linux
sudo apt install tmux
-
On macOS
brew install tmux
Create a basic configuration at ~/.tmux.conf:
# Change prefix from 'Ctrl+b' to 'Ctrl+a' (optional)
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix
# Split panes using | and -
bind | split-window -h
bind - split-window -v
unbind '"'
unbind %
# Set vi-mode for navigating buffers
setw -g mode-keys vi
# Increase history limit
set-option -g history-limit 5000
# Theme and status bar customization
set -g status-style 'bg=#333333 fg=#5eacd3'
Integrating Tmux with Vim
If you're using Vim, the vim-tmux-navigator plugin allows for seamless navigation between Vim splits and tmux panes:
In your .vimrc:
Plugin 'christoomey/vim-tmux-navigator'
And in your .tmux.conf:
# Smart pane switching with awareness of Vim splits
is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
| grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h' 'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
Fully Rust(y)? Meet Zellij
Even more into terminal development and in the "use all Rust"? A tool worth mentioning is zellij (https://zellij.dev/), that works pretty well with Helix. My muscle memory is pretty tied up with Vim and Tmux, but it is definitely a tool worth checking.
Editor options
Now for the core of the development environment - the text editor. Let's explore two similar options: Vim (with plugins) and Helix.
Option 1: Vim for Rust Development
vIM is a highly configurable text editor built to make creating and changing any kind of text very efficient. For Rust development, we'll need several plugins to create an IDE-like experience.
Installation
-
On Linux (most distros has already Vim installed)
sudo apt install vim vim-gtk3
(vim-gtk3 includes system clipboard support)
-
On macOS
brew install Vim
Essential Configuration for Rust
First, we'll set up Vim with a plugin manager. I use Vundle but there are many others.
-
Install Vundle
git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim
My .vimrc configuration (with some omissions):
set nocompatible " be iMproved, required
filetype off " required
" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()
" let Vundle manage Vundle, required
Plugin 'VundleVim/Vundle.vim'
" File navigation and search
Plugin 'junegunn/fzf'
Plugin 'junegunn/fzf.vim'
Plugin 'preservim/nerdtree' " File explorer
Plugin 'jistr/vim-nerdtree-tabs'
Plugin 'xolox/vim-misc'
" Requires CTAGS - https://github.com/universal-ctags/ctags
" Plugin 'majutsushi/tagbar' NEW NAME
Plugin 'preservim/tagbar'
Plugin 'nanotech/jellybeans.vim'
Plugin 'vim-airline/vim-airline'
Plugin 'vim-airline/vim-airline-themes'
"
" ----- Plugin Rust dev
Plugin 'rust-lang/rust.vim'
Plugin 'shougo/echodoc.vim' " doc echo, params list while typing
Plugin 'dense-analysis/ale' " LSP Engine
" Haskell dev
...
" Scala dev
...
" Code completion
Plugin 'prabirshrestha/async.vim'
Plugin 'prabirshrestha/vim-lsp' " Language Server Protocol
Plugin 'prabirshrestha/asyncomplete.vim'
Plugin 'prabirshrestha/asyncomplete-lsp.vim'
Plugin 'mattn/vim-lsp-settings' " Auto-configure LSP
" --- Working with Git ---
Plugin 'tpope/vim-fugitive'
Plugin 'airblade/vim-gitgutter'
Plugin 'gregsexton/gitv'
" Plugin 'ErichDonGubler/vim-sublime-monokai'
Plugin 'morhetz/gruvbox'
" All of your Plugins must be added before the following line
call vundle#end() " required
filetype plugin indent on " required
set omnifunc=syntaxcomplete#Complete
" NERDTree settings START -----------------------------------
" open NERDTree automatically when Vim starts
autocmd vimenter * NERDTree
autocmd StdinReadPre * let s:std_in=1
autocmd VimEnter * if argc() == 1 && isdirectory(argv()[0]) && !exists("s:std_in") | exe 'NERDTree' argv()[0] | wincmd p | ene | endif
" use Ctrl+n to toggle NERDTree
map <C-n> :NERDTreeToggle<CR>
"close Vim if the only window left open is NERDTree
autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif
let g:NERDTreeDirArrowExpandable = '▸'
let g:NERDTreeDirArrowCollapsible = '▾'
let g:nerdtree_tabs_open_on_console_startup=1
let g:airline#extensions#tabline#enabled = 1
"
" NERDTree settings END -------------------------------------------
"
" Move between buffers
map <leader>n :bnext<cr>
map <leader>p :bprevious<cr>
map <leader>d :bdelete<cr>
" OPEN TERMINAL inside Vim ----------------------------------
" Open a terminal in another window
" bel term = below, open the terminal in the below window
map <silent> <leader>t :bel term<cr>
" Open a terminal in the current window
map <silent><leader><C-t> :term ++curwin<cr>
" ---------------------
" Enable auto completion menu after pressing TAB.
set wildmenu
" Make wildmenu behave like similar to Bash completion.
set wildmode=list:longest
" There are certain files that we would never want to edit with Vim.
" Wildmenu will ignore files with these extensions.
set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx
" ALE Config start --------------------------------
let g:ale_linters = { 'rust' : ['analyzer'],}
let g:ale_fixers = {'rust': ['rustfmt']}
set completeopt=menu,menuone,preview,noselect,noinsert
let g:ale_completion_enabled = 0 " Use asyncomplete instead
let g:ale_set_highlights = 1
let g:ale_fix_on_save = 1
" linter NOT running while writing
let g:ale_lint_on_text_changed = 'never'
" linter NOT running just after opening a file
let g:ale_lint_on_enter = 0
let g:ale_lint_on_save = 1
let g:ale_virtualtext_cursor = 2
let g:ale_hover_to_floating_preview = 1
let g:ale_hove_cursor = 1
" ALE signs
let g:ale_sign_error = '✘'
let g:ale_sign_warning = '⚠'
" LSP configuration for Rust
if executable('rust-analyzer')
au User lsp_setup call lsp#register_server({
\ 'name': 'rust-analyzer',
\ 'cmd': {server_info->['rust-analyzer']},
\ 'allowlist': ['rust'],
\ 'initialization_options': {
\ 'cargo': {
\ 'allFeatures': v:true,
\ },
\ 'checkOnSave': {
\ 'command': 'clippy',
\ },
\ 'procMacro': {
\ 'enable': v:true,
\ },
\ },
\ })
endif
" LSP mappings
function! s:on_lsp_buffer_enabled() abort
setlocal omnifunc=lsp#complete
setlocal signcolumn=yes
nmap <buffer> gd <plug>(lsp-definition)
nmap <buffer> gr <plug>(lsp-references)
nmap <buffer> gi <plug>(lsp-implementation)
nmap <buffer> <leader>rn <plug>(lsp-rename)
nmap <buffer> [g <plug>(lsp-previous-diagnostic)
nmap <buffer> ]g <plug>(lsp-next-diagnostic)
nmap <buffer> K <plug>(lsp-hover)
endfunction
augroup lsp_install
au!
autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
" Hover - show info about the keyword at the cursor
nmap <silent> <leader>h :ALEHover<CR>
" GOTO Definiotn, Show References - Keys binding
nmap <silent> <F12> :ALEGoToDefinition<CR>
" Ctrl + F12
nmap <silent> <C-F12> :ALEFindReferences<CR>
" Previous and next Error - Keys binding
" Shift + F8
nmap <silent> <S-F8> <Plug>(ale_previous_wrap)
nmap <silent> <F8> <Plug>(ale_next_wrap)
" ALE Config End -------------------------------
" echodoc Config Start -------------------------------
let g:echodoc#enable_at_startup = 1
let g:echodoc#type = 'popup'
" To use a custom highlight for the popup window,
" change Pmenu to your highlight group
highlight link EchoDocPopup Pmenu
" echodoc Config End -------------------------------
" ----- majutsushi/tagbar settings -----
" Open/close tagbar with \b
nmap <silent> <leader>b :TagbarToggle<CR>
" Uncomment to open tagbar automatically whenever possible
"autocmd BufEnter * nested :call tagbar#autoopen(0)
" ----- airblade/vim-gitgutter settings -----
" Required after having changed the colorscheme
" hi clear SignColumn
" In vim-airline, only display "hunks" if the diff is non-zero
"let g:airline#extensions#hunks#non_zero_only = 1
" Airline settings
let g:airline_powerline_fonts = 1
let g:airline#extensions#tabline#enabled = 1
let g:airline#extensions#ale#enabled = 1
" --- jellybeans color and font settings ---
let g:jellybeans_use_lowcolor_black = 0
set guifont=Monaco:h10 noanti
" jellybeans color and font settings END
set tabstop=2 "A tab is 4 spaces
set expandtab "Always uses spaces instead of tabs
set softtabstop=2 "Insert 2 spaces when tab is pressed
set shiftwidth=2 "An indent is 2 spaces
set shiftround "Round indent to nearest shiftwidth multiple
""""""""""""""""""""""""""""""""""""
" Appearance
"""""""""""""""""""""""""""""""""""""
set t_Co=256
let g:gruvbox_italic=1
colorscheme gruvbox
" colorscheme relaxedgreen
" colorscheme sublimemonokai
set background=dark
set cursorline
set number
set laststatus=2
set showtabline=1
set number relativenumber
set ruler
set showcmd
set incsearch
set hlsearch
set ignorecase
set smartcase
set encoding=utf-8
...
syntax on
syntax enable
hi! Normal ctermbg=NONE guibg=NONE
hi! NonText ctermbg=NONE guibg=NONE
set clipboard=unnamed
This configuration provides:
- Language Server Protocol (LSP) integration via rust-analyzer
- Code completion with asyncomplete
- Linting and diagnostics with ALE
- Fuzzy finding with FZF
- Git integration with Fugitive and GitGutter
- File navigation with NERDTree
- Syntax highlighting with Vim's syntax engine
- Enhanced status line with Airline
- and so on
Installing and Configuring Rust-Analyzer For the LSP support to work correctly, you need to have rust-analyzer installed and available in your PATH:
- Install Rust Analyzer
rustup component add rust-analyzer
If that doesn't work, you can download the binary directly Check the rust-analyzer GitHub page for the latest release URL
Option 2: Helix Editor
Helix is a post-modern terminal-based editor inspired by Kakoune, designed with a modal editing philosophy but with a more intuitive key scheme. It has excellent out-of-the-box support for Rust.
Installation and basic configuration
-
On Linux
curl -fsSL https://raw.githubusercontent.com/helix-editor/helix/master/install.sh | bash
-
On macOS
brew install helix
Configuration
Create ~/.config/helix/config.toml:
theme = "mytheme"
[editor]
line-number = "relative"
mouse = false
bufferline = "multiple"
auto-format = false
text-width = 130
[editor.cursor-shape]
insert = "bar"
normal = "block"
select = "underline"
[editor.file-picker]
hidden = false
[editor.indent-guides]
render = true
[editor.lsp]
display-messages = true
auto-signature-help = true
display-inlay-hints = true
[editor.statusline]
left = ["mode", "spinner"]
center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"
mode.normal = "NORMAL"
mode.insert = "INSERT"
mode.select = "SELECT"
[keys.normal]
# Add custom keybindings here
g = { a = "code_action" } # Quick access to code actions
space = { w = ":w", q = ":q" }
C-p = "file_picker"
C-f = "search_selection"
Creating a dedicated ~/.config/helix/languages.toml for Rust:
[[language]]
name = "rust"
comment-token = "//"
auto-format = true
indent = { tab-width = 4, unit = " " }
[language.auto-pairs]
'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
[language.config]
# Customize Rust Analyzer settings
checkOnSave = { command = "clippy" }
cargo = { allFeatures = true }
procMacro = { enable = true }
inlayHints = { bindingModeHints = true, chainingHints = true, typeHints = true }
The beauty of Helix is that it requires minimal configuration to get powerful Rust support. The core features include:
- Strong selection-based editing model
- Multiple cursor support
- Tree-sitter integration for syntax highlighting
- Built-in LSP client
- Powerful fuzzy-matcher
- Git integration
- Integration with different LLMs with custom plugins
Fuzzy Finding with FZF
FZF (Fuzzy Finder) is a versatile command-line fuzzy finder that can be integrated with various tools in your development environment.
Installation
-
On Linux
sudo apt install fzf
-
On macOS
brew install fzf
Basic Configuration
Add to your .bashrc or .zshrc:
# fzf configuration
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
# Key bindings for bash
$ source /usr/share/doc/fzf/examples/key-bindings.bash
$ source /usr/share/doc/fzf/examples/completion.bash
# For zsh users
$ source /usr/share/fzf/key-bindings.zsh
$ source /usr/share/fzf/completion.zsh
FZF can be used directly in the terminal:
CTRL-T to paste the selected files/directories onto the command line CTRL-R to paste the selected command from history
ALT-C to cd into the selected directory
It also integrates beautifully with Vim through the fzf.vim plugin, allowing you to search files, buffers, and even grep through code content.
Setting Up Rust Tools
To get the most out of your Rust development environment, you'll need several core Rust tools:
# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add Rust to your path
source $HOME/.cargo/env
# Install core components
rustup component add rust-analyzer
rustup component add rustfmt
rustup component add clippy
Install useful cargo extensions
cargo install cargo-edit # Add, remove, upgrade dependencies
cargo install cargo-watch # Watch for changes and run commands
cargo install cargo-expand # Show result of macro expansion
cargo install cargo-audit # Audit dependencies for vulnerabilities
cargo install cargo-update # Update installed binaries
Putting It All Together
Let's create a workflow example that combines all these tools for Rust development:
#Start a new terminal session:
alacritty
#Create a new tmux session:
$ tmux new -s rust-dev
#Split the window into panes:
# Use your tmux prefix (Ctrl+a in our config) followed by:
<prefix> | # Split vertically
<prefix> - # Split horizontally
#Navigate and set up your workspace:
#Left pane: Vim for code editing
#Upper right: Cargo watch for continuous testing
#Lower right: Command line for Git operations and cargo commands
#Create a new project:
$ cargo new my-project
$ cd my-project
#Start coding with Vim:
$ vim src/lib.rs
#Set up continuous testing in the upper right pane:
$ cargo watch -x "test -- --nocapture"
What about AI?
Nowadays, avoiding talking about AI and LLM in tech-related discussions is pretty impossible. I don't use them too much. Languages like Rust or Haskell are still not very popular, so the data available for the bot is limited, resulting usually in poor suggestions. Also, very popular languages like Python or Javascript are updated often, so sometimes the LLM suggestions are a bit... stale.
I do use bots and LLMs, more like a smart advisor or helper, with their powerful capability to produce deep and interesting search results.
Nevertheless, both Vim and Helix have plugins supporting the integration of Copilot-like tools. I'm testing some of them these days, but not ready to use in my daily routine.
Conclusion
Building a terminal-based Rust development environment gives you tremendous power and flexibility. Whether you choose Vim with its rich ecosystem of plugins or Helix with its streamlined, Rust-friendly design, you'll have access to modern IDE features while maintaining the speed and efficiency that terminal tools offer.
The configuration for original Vim maintains its classic simplicity while adding the modern capabilities needed for productive Rust programming.
As you grow more comfortable with this environment, you can further customize it to match your exact workflow. The plugin ecosystem is always growing and maturing.
The principles-first approach is well-served by these tools, as they encourage thoughtful architecture and clear separation of concerns in your code. With these tools properly configured, you'll have a development environment that's both powerful and enjoyable to use.
Happy hacking!