Skip to main content

(Almost) All You Need to Know About Variables

This is the start of a series of posts about Emacs Lisp concepts in which I will try to fill some gaps between beginner resources and some more advanced concepts. This post assumes that you are already familiar with common Emacs terminology and know how to read and evaluate basic snippets of Emacs Lisp code. Ideally you should also have heard of variable scope and how it works in some other programming language. The examples also assume you use a fairly recent Emacs version (v.25 or later).

Of course the Elisp manual will tell you everything there is to know but it is designed for people who already know what they are looking for (and it is really great for that). Therefore many people ask for resources explaining Elisp concepts at a higher level and reduce the information to the most useful bits. This is my attempt to bring readers in the position of having a good grasp of the basics so they can use them for their own configuration and have an easier time if they need to look up some detail in the manual.

Global Variables

User options defined via defcustom and variables defined via defvar or defconst are global. One important aspect about variables declared by defcustom or defvar is that reevaluating them won’t reset the variable if it is already bound. For example if you establish a binding for my-var in you init file like this:

(setq my-var nil)

Then evaluating the following form won’t reset the variable to t:

(defvar my-var t)

Note that there is one exception: If you evaluate the above declaration via C-M-x which calls eval-defun the value will be reset to t. This way you can enforce setting the value if you need to. This behavior is intentional: As you might know many features in Emacs are only loaded on demand (autoloaded). If the declarations in those files would reset variables to their default value this would override any settings you made in your init.

User Options

A user option is simply a global variable that was declared with defcustom. Unlike variables declared via defvar such a variable is configurable via the M-x customize interface. As far as I know most people don’t make much use of it because it feels clunky and once you know how to set variables in your init file there is no compelling reason to use it. One detail many users aren’t aware of is that setting user options via customize might execute code, which is sometimes used to run additional setup instructions:

(defcustom my-option t
  "My user option."
  :set (lambda (sym val)
         (set-default sym val)
         (message "Set %s to %s" sym val)))

If you evaluate the above piece of code and change the value using the customize interface via M-x customize-option RET my-option RET, you will see that the lambda gets called and the message in the echo area tells you the symbol and value of the option.

In case you use setq in your int file for changing the value of such an option the setter function will not run. To set such an option correctly via Elisp you need to use the function customize-set-variable. Alternatively there are various versions of csetq macros people use in their configs to automatically take care of this (you can use github code search to discover more sophisticated variants if you like):

(defmacro csetq (sym val)
  `(funcall (or (get ',sym 'custom-set) 'set-default) ',sym ,val))

If you are using use-package, the :custom keyword already handles this for you.

After putting the above code in your init file, you can use csetq to set variables in a way which respect any existing setter functions. You can prove this to yourself by watching the message in the echo area when using this macro to change the option defined above:

(csetq my-option nil)

Dynamic Binding and Lexical Binding

Even if you have already dealt with other programming languages you might never heard of dynamic or lexical binding. This is because most programming languages today use lexical binding and when you learn about variable scope/lookup there is no need to know the difference.

Emacs Lisp is a bit special in this regard because dynamic binding is the default and lexical binding needs to be enabled explicitly. This has historical reasons and in practice you should always enable lexical binding because it is faster and less error prone. To enable it you simply put the following comment line as the first line of your Emacs Lisp file:

;;; -*- lexical-binding: t; -*-

Alternatively you can call M-x add-file-local-variable-prop-line which will insert this line for you when you choose the variable lexical-binding with value t.

When a file with such a special formatted line gets loaded Emacs will set the variable accordingly, which means the code in that buffer will be loaded with lexical binding enabled. Interactively you can use M-x eval-buffer which will also take the lexical binding setting into account.

Now you know how to enable lexical binding let’s examine what those terms actually mean: With dynamic binding the latest binding established during program execution will be used for variable lookup. You can test this by putting the following code in an empty buffer and execute M-x eval-buffer:

(defun a-exists-only-in-my-body (a)
  (other-function))

(defun other-function ()
  (message "I see `a', its value is %s" a))

(a-exists-only-in-my-body t)

You may be surprised to see that the lookup of variable a in other-function will succeed.

If you retry the preceding example with the special lexical-binding comment at the top, you will notice that the code will throw a variable is void error because other-function does not know about the a variable. Coming from other programming languages this is the behavior you would expect.

With lexical binding the scope is defined by the surrounding source code. In addition to performance reasons, experience and time have shown that this behavior should be preferred.

Special Variables and Dynamic Binding

As you might know let is used to temporary establish local bindings:

(let ((a "I'm a")
      (b "I'm b"))
  (message "Hello, %s. Hello %s" a b))

Here is the thing: Variables declared via defcustom, defvar or defconst are so called special variables and those will continue to use dynamic binding regardless if lexical binding is enabled or not:

;;; -*- lexical-binding: t; -*-

(defun some-other-function ()
  (message "I see `c', its value is: %s" c))

(defvar c t)

(let ((a "I'm lexically bound")
      (c "I'm special and therefore dynamically bound"))
  (some-other-function)
  (message "I see `a', its values is: %s" a))

To see both messages in the example above you can switch to the *Messages* buffer using C-h e.

Local variables bound with let or function arguments will follow the lookup rules as defined by the lexical-binding variable, but global variables defined via defvar, defconst or defcustom can be changed deep down the call stack for the duration of the let body!

This behavior allows for convenient adhoc customizations and is often used in Emacs, which isn’t surprising given that Emacs Lisp started out with dynamic binding being the only option. Here is a common example showing how you can temporary write to some read-only buffer:

(let ((inhibit-read-only t))
  (insert ...))

Or another often seen example to perform case sensitive searches:

(let ((case-fold-search nil))
  (some-function-which-uses-search ...))

Dynamic binding allows changing the behavior of functions in ways which the authors of those functions may have never anticipated. It’s a powerful tool and it is a great feature for a program that is designed and used like Emacs.

There is one caveat you should be aware of: It may happen that you accidentally use a local variable name which is a declared as a special variable elsewhere. One trick to prevent such conflicts is to avoid local variables which have dashes in their name. In my current Emacs session this leaves only a handful of potential conflicting candidates:

(let ((vars ()))
  (mapatoms
   (lambda (cand)
     (when (and (boundp cand)
                (not (keywordp cand))
                (special-variable-p cand)
                (not (string-match "-"
                                   (symbol-name cand))))
       (push cand vars))))
  vars) ;; => (t obarray noninteractive debugger nil)

Buffer Local Variables

Each buffer can have a local binding for a variable. This means any variable lookup made while this buffer is current will reveal the buffer local value of that variable instead of the default one. Local variables are an important feature in Emacs, for example they are used by major modes to establish their buffer local behavior and settings.

You have already seen a buffer local variable in this post: The special comment line for lexical-binding which binds it buffer locally to t. In Emacs such buffer local variables defined in special comment lines are also called file local variables.

Any global variable can be shadowed by a buffer local one. Take for example the my-var variable defined in a previous section of this post which you can set locally like this:

(setq-local my-var t)
;; or (set (make-local-variable 'my-var) t)

my-var will be local to the buffer which is current when you evaluate the code above. If you call describe-variable on it, you will see that the documentation tells you the local value and the global one. Programmatically you can check the local value using buffer-local-value and the default one with default-value. To remove the local version you could invoke M-x kill-local-variable.

Another important property you need to be aware of is that once a variable is buffer local, any further uses of setq (while this buffer is current) will continue to set the local value. To set the default value you would need to use setq-default.

Because local variables are meant for buffer customization you will use them most often in mode hooks. A typical example would be something like:

(add-hook 'go-mode-hook
          (defun go-setup+ ()
            (setq-local compile-command
              (if (string-suffix-p "_test.go" buffer-file-name)
                  "go test -v"
                (format "go run %s"
                        (shell-quote-argument
                         (file-name-nondirectory buffer-file-name)))))))

This will set the compile command used by M-x compile for go mode buffers.

Another important aspect is that some variables are automatically buffer local. This means as soon as you setq such a variable this will establish a local binding for the current buffer. This feature shouldn’t be used often because this implicit behavior isn’t nice but if you want you can create such automatically local variables like this:

(defvar-local my-automatical-local-var t)
;; or (make-variable-buffer-local 'my-automatical-local-var)

The variable indent-tabs-mode is a built-in example of this. If you would use setq in your init file to change the value of this variable that wouldn’t affect the default value at all. Only the value for the buffer that is current while loading your init file would be changed. Because of that you need to use setq-default to change the default value of indent-tabs-mode.

Closing Words

I have tried my best to find a balance between details and a more high level overview. Hopefully this helps some souls out there feeling more confident when setting their variables in Emacs. Please leave a comment if you have any questions or suggestions.

See you in parendise!

Comments on Reddit