How to setup Emacs for Rust development

In this post I will show you how to setup Emacs for the Rust development. We will start with empty setup and then move iteratively adding new features. At the end of the process you should have something similar to the following screenshot:

Emacs: Rust code autocomplete

Initial setup

Emacs loads it’s configuration from initialization file which can be ~/.emacs, ~/.emacs.el, or .emacs.d/init.el.

We will use .emacs.d/init.el file as configuration script. Let’s create it:

$ mkdir ~/.emacs.d
$ cd ~/.emacs.d
$ touch init.el
$ git init .

Here, we created initialization file as well as new git repository inside .emacs.d directory. Git step is optional and it has nothing to do with Emacs configuration. However, I strictly advise not to skip it, because putting your configuration directory under the source control allows you to have version tracking, rollbacks, backups (just create a new repository on http://bitbucket.com or similar resource and sync your local repository with the remote one).

Now, create ~/.emacs.d/.gitignore file with the following content:

~/.emacs.d/.gitignore
*~
places
\.recentf
\.smex-items
ido.last
projectile-bookmarks.eld
projectile.cache
auto-save-list
backups
elpa/
eshell

Commit your changes to the source control.

What to put into init.el file

You can put all your configuration inside Emacs initialization file. However this is not a best idea. As we know from software development it is much better to group similar things into modules/packages/you-name-it to simplify future reuse, changes, etc.

We will use init.el file as an entry point that loads all other scripts.

Open init.el file and add the following code to it:

~/.emacs.d/init.el
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

(provide 'init)

Here we tell Emacs that we will put all our custom scripts into ~/.emacs.d/lisp folder so that Emacs can find them when we use require function to load these scripts. You can read more about loading scripts in Emacs Lisp here.

Packages

Package is an Emacs Lisp program that provides additional features. You can read about Emacs packages here.

In fact all our scripts will be Emacs packages because this simplifies dependency management and has ultra-speed load time.

Add (require 'init-elpa) line to the ~/.emacs.d/init.el script. Now it should look like this:

~/.emacs.d/init.el
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

(require 'init-elpa)

(provide 'init)

This line will load ~/.emacs.d/lisp/init-elpa.el and all its dependencies.

Now create ~/.emacs.d/lisp/init-elpa.el file, open it for editing and put the following code in it:

~/.emacs.d/lisp/init-elpa.el
(require 'package)

(defun require-package (package)
  "Install given PACKAGE if it was not installed before."
  (if (package-installed-p package)
      t
    (progn
      (unless (assoc package package-archive-contents)
	(package-refresh-contents))
      (package-install package))))

(add-to-list 'package-archives
	     '("melpa" . "https://melpa.org/packages/"))

(package-initialize)

(provide 'init-elpa)

This package initializes Emacs package system, adds MELPA the list of available package archives and defines a function require-package that installs the given package if it was not installed before.

Exec path [OS X only]

The next step is specific to OS X, where an Emacs instance started from the GUI will have a different environment than a shell in a terminal window, because OS X does not run a shell during the login. Obviously this will lead to unexpected results when calling external utilities like make from Emacs.

So here we are going to solve this issue by using exec-path-from-shell package.

Add the line (require 'init-exec-path) after the last require in the ~/.emacs.d/init.el file:

~/.emacs.d/init.el
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

(require 'init-elpa)
(require 'init-exec-path)

(provide 'init)
In the future, when I say add require to the initialization file it means add it to the ~/.emacs.d/init.el file after the last require as in previous example.

Now open ~/.emacs.d/lisp/init-exec-path.el and fill it with the following code:

~/.emacs.d/lisp/init-exec-path.el
(require 'init-elpa)

(require-package 'exec-path-from-shell)
(when (memq window-system '(mac ns x))
  (exec-path-from-shell-initialize))

(provide 'init-exec-path)

First, we require 'init-elpa package we created before to use its require-package function.

Second, we execute exec-path-from-shell-initialize function only if this instance of Emacs is run under the OS X.

UI

Now it’s time to deal with default Emacs UI. Add (require 'init-ui) to Emacs initialization file, then open ~/.emacs.d/lisp/init-ui.el and add the following lines:

~/.emacs.d/lisp/init-ui.el
(require 'init-elpa)
(require-package 'atom-one-dark-theme)
(require-package 'golden-ratio)

(require 'golden-ratio)

(setq inhibit-startup-message t)
(menu-bar-mode -1)
(when (fboundp 'tool-bar-mode)
  (tool-bar-mode -1))
(when (fboundp 'scroll-bar-mode)
  (scroll-bar-mode -1))

(set-face-attribute 'default nil :height 140)
(setq-default line-spacing 0.4)

(setq
      x-select-enable-clipboard t
      x-select-enable-primary t
      save-interprogram-paste-before-kill t
      apropos-do-all t
      mouse-yank-at-point t)

(load-theme 'atom-one-dark t)

(blink-cursor-mode 0)
(setq-default cursor-type 'bar)
(set-cursor-color "#cccccc")
(setq ring-bell-function 'ignore)

(golden-ratio-mode 1)

(provide 'init-ui)

There is a little bit more code in this package than in the previous. It turns off menu bar, scroll bar, and tool bar, as well as standard welcome message. It installs and loads Atom One dark syntax theme.

Another important feature is Golden Ratio. It resizes Emacs windows automatically to make the window that has the focus to have perfect size for editing.

Editing

The next step is to make editing a little bit more convenient.

Add (require 'init-editing) to the initialization file, open ~/.emacs.d/lisp/init-editing.el script and add the following:

~/.emacs.d/lisp/init-editing.el
(require 'init-elpa)
(require 'saveplace)
(require-package 'rainbow-delimiters)
(require-package 'flycheck)

;; Highlights matching parenthesis
(show-paren-mode 1)

;; Highlight current line
(global-hl-line-mode 1)

;; Interactive search key bindings. By default, C-s runs
;; isearch-forward, so this swaps the bindings.
(global-set-key (kbd "C-s") 'isearch-forward-regexp)
(global-set-key (kbd "C-r") 'isearch-backward-regexp)
(global-set-key (kbd "C-M-s") 'isearch-forward)
(global-set-key (kbd "C-M-r") 'isearch-backward)

(define-key global-map (kbd "RET") 'newline-and-indent)

(add-hook 'after-init-hook #'global-flycheck-mode)

;; When you visit a file, point goes to the last place where it
;; was when you previously visited the same file.
;; http://www.emacswiki.org/emacs/SavePlace

(setq-default save-place t)
;; keep track of saved places in ~/.emacs.d/places
(setq save-place-file (concat user-emacs-directory "places"))

;; Emacs can automatically create backup files. This tells Emacs to
;; put all backups in ~/.emacs.d/backups. More info:
;; http://www.gnu.org/software/emacs/manual/html_node/elisp/Backup-Files.html
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory
                                               "backups"))))
(setq auto-save-default nil)

(defun toggle-comment-on-line ()
  "Comment or uncomment current line."
  (interactive)
  (comment-or-uncomment-region (line-beginning-position) (line-end-position)))
(global-set-key (kbd "C-;") 'toggle-comment-on-line)

(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)

(provide 'init-editing)

It is pretty good documented. Just go through it and read the source :)

Add (require 'init-navigation) to the initialization file and open ~/.emacs.d/lisp/init-navigation.el script.

We are going to install and configure the following packages:

  • Interactively Do Things or Ido mode. I advise you to read Introduction to Ido Mode.

  • ido-ubiquitous mode. This package replaces stock emacs completion with Ido completion wherever it is possible to do so without breaking things.

  • Smex. Built on top of Ido, it provides a convenient interface to your recently and most frequently used commands. And to all the other commands, too.

  • Recentf. Recentf is a minor mode that builds a list of recently opened files. This list is automatically saved across sessions on exiting Emacs - you can then access this list through a command or the menu.

  • Projectile. It provides easy project management and navigation. Please refer to the link I’ve provide for complete list of features.

And here is the code:

~/.emacs.d/lisp/init-navigation.el
(require 'init-elpa)
(require 'ido)
(require 'recentf)
(require-package 'ido-ubiquitous)
(require-package 'smex)
(require-package 'projectile)

(setq recentf-save-file (concat user-emacs-directory ".recentf"))
(recentf-mode 1)
(setq recentf-max-menu-items 40)

(ido-mode t)
(setq ido-enable-flex-matching t)
(setq ido-use-filename-at-point nil)
(setq ido-auto-merge-work-directories-length -1)
(setq ido-use-virtual-buffers t)

(ido-ubiquitous-mode 1)

;; Shows a list of buffers
(global-set-key (kbd "C-x C-b") 'ibuffer)

(setq smex-save-file (concat user-emacs-directory ".smex-items"))
(smex-initialize)
(global-set-key (kbd "M-x") 'smex)

(projectile-global-mode)

;; Enable move point from window to window using Shift and the arrow keys
(windmove-default-keybindings)

(provide 'init-navigation)

Miscellaneous

Here we will setup a few things to make Emacs to be more convenient to use.

Add (require 'init-miscellaneous) to the initialization file and open the ~/.emacs.d/lisp/init-miscellaneous.el for editing. Add the following lines:

~/.emacs.d/lisp/init-miscellaneous.el
;; Changes all yes/no questions to y/n type
(fset 'yes-or-no-p 'y-or-n-p)

;; No need for ~ files when editing
(setq create-lockfiles nil)

(provide 'init-miscellaneous)

It changes yes/no questions to y/n type so that you can just press y or n, instead of typing yes or no. Also this packages disables generation of Emacs lock files.

Text Completion

Emacs has very good text completion framework called company-mode. Let’s install and configure it.

Add (require 'init-company-mode) to the initialization file as usual, then open ~/.emacs.d/lisp/init-company-mode.el script and add these lines:

~/.emacs.d/lisp/init-company-mode.el
(require 'init-elpa)
(require-package 'company)
(require 'company)

(setq company-tooltip-align-annotations t)
(add-hook 'prog-mode-hook 'company-mode)

(provide 'init-company-mode)

Here, we enable company mode for all programming modes making it default autocompletion engine.

Now save everything and commit to the source control.

Rust Support

Please, make sure that you have Rust installed before continue.

Now here is the thing. We have to install third-party tools to make Rust autocompletion work.

Install Racer

Please, follow the Racer installation guideline to install it on your machine.

Install Rust sources

Clone the https://github.com/rust-lang/rust repository.

Configure Rust Support in Emacs

As usual, add (require 'init-rust) to the initialization file and add the following code to the ~/.emacs.d/lisp/init-rust.el package:

~/.emacs.d/lisp/init-rust.el
(require 'init-elpa)
(require-package 'company)
(require-package 'racer)
(require-package 'rust-mode)
(require-package 'flycheck)
(require-package 'flycheck-rust)

(require 'company)
(require 'racer)
(require 'rust-mode)
(require 'electric)
(require 'eldoc)
(require 'flycheck)
(require 'flycheck-rust)

(add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode))
(add-hook 'rust-mode-hook  #'company-mode)
(add-hook 'rust-mode-hook  #'racer-mode)
(add-hook 'racer-mode-hook #'eldoc-mode)
(add-hook 'flycheck-mode-hook #'flycheck-rust-setup)
(add-hook 'rust-mode-hook
          '(lambda ()
	     (setq racer-cmd (concat (getenv "HOME") "/.rust-dev/racer/target/release/racer"))
	     (setq racer-rust-src-path (concat (getenv "HOME") "/.rust-dev/rust/src"))
             (local-set-key (kbd "TAB") #'company-indent-or-complete-common)
	     (electric-pair-mode 1)))

(provide 'init-rust)

This package installs the following dependencies:

  • Flycheck. This is a modern on-the-fly syntax checking extension.

  • flycheck-rust. It adds Rust syntax checking and linting to Flycheck.

  • rust-mode. This adds Rust syntax highlighting, etc.

  • racer. It allows Emacs to use Racer for Rust code completion and navigation.

Please, change the path to the racer executable (racer-cmd variable) and the path to the Rust sources (racer-rust-src-path variable) according to your local environment.

For example, I downloaded racer to the ~/.rust-dev/racer folder and Rust sources to the ~/.rust-dev/rust.

Save everything, commit to the source control, start Emacs. Wait until it finishes installing missing packages, then restart it.

Now you should have Rust syntax highlighting, autocomplete, and project navigation in your beloved Emacs.

You can find my Emacs config here.

That’s it.