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 loads it’s configuration from initialization file which can be
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
~/.emacs.d/.gitignore file with the following content:
*~ 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.
init.el file and add the following code to it:
(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
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
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.
(require 'init-elpa) line to the
~/.emacs.d/init.el script. Now it
should look like this:
(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.
~/.emacs.d/lisp/init-elpa.el file, open it for editing and put the
following code in it:
(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
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
Add the line
(require 'init-exec-path) after the last
require in the
(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/lisp/init-exec-path.el and fill it with the following code:
(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)
'init-elpa package we created before to use its
Second, we execute
exec-path-from-shell-initialize function only
if this instance of Emacs is run under the OS X.
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
(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.
The next step is to make editing a little bit more convenient.
(require 'init-editing) to the initialization file, open
~/.emacs.d/lisp/init-editing.el script and add the following:
(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 :)
(require 'init-navigation) to the initialization file and open
We are going to install and configure the following packages:
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:
(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)
Here we will setup a few things to make Emacs to be more convenient to use.
(require 'init-miscellaneous) to the initialization file and open the
~/.emacs.d/lisp/init-miscellaneous.el for editing. Add the following lines:
;; 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)
yes/no questions to
y/n type so that you can just press
instead of typing
no. Also this packages disables generation of
Emacs lock files.
Emacs has very good text completion framework called company-mode. Let’s install and configure it.
(require 'init-company-mode) to the initialization file as usual, then
~/.emacs.d/lisp/init-company-mode.el script and add these lines:
(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.
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.
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
(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:
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
For example, I downloaded
racer to the
~/.rust-dev/racer folder and Rust
sources to the
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.