I'm writing an emacs major mode, which uses buffer-local variables to store some state:

(defun foo-mode ()
  "My nice major mode"
  (interactive)
  (kill-all-local-variables)
  (setq mode-name "foo")
  (setq major-mode 'foo-mode)
  (set (make-local-variable 'foo-state) "bar"))

(defun foo-change-state ()
  (setq foo-state "baz"))

This works very well and has the property that in any buffer not using my major mode, the foo-state variable is not bound (which is a good thing in my opinion, since it avoids cluttering the symbols table).

However, byte-compiling such a piece of code produces the following warning:

Warning: assignment to free variable `foo-state'

Using defvar gets rid of the warning, but has the side-effect that foo-state is now bound everywhere, which is undesirable in my opinion.

Is there a way to get rid of the warnings while still not binding the mode-specific variables in every buffer? Or am I mistaken when I think these variables should not be declared globally?

  • 1
    See <kbd>C-h</kbd><kbd>i</kbd><kbd>g</kbd> (elisp) Warning Tips <kbd>RET</kbd> – phils Apr 6 '14 at 21:30
  • 6
    Well that was a particularly unhelpful answer-to-comment conversion by someone. To make the above readable again: C-h i g (elisp) Warning Tips RET is where the documentation which answers this question lives. – phils Apr 8 '14 at 3:34
  • @phils Yes. I, for one, did like your answer. Thanks! – Francesco Apr 8 '14 at 6:22
  • BTW, I recommend you use define-derived-mode, as in (define-derived-mode foo-mode nil "foo" "My nice major mode." (set (make-local-variable 'foo-state) "bar")). – Stefan Apr 16 '14 at 1:03
  • 1
    Beware! The Warning Tips section in the Emacs Manual contains a blatant lie regarding defvar: "Such a definition has no effect except to tell the compiler not to warn about uses of the variable [...] in this file.". It most certainly can have more impact than just silencing the compiler: "[it] also declares the variable as "special", so that it is always dynamically bound even if ‘lexical-binding’ is t." (quote taken from the actual documentation for defvar). Perhaps the advice in Warning Tips was written before the introduction of lexical binding in Emacs Lisp... – ack Mar 1 at 13:27
up vote 39 down vote accepted

The official way to do what you want is (defvar foo-state). Note the absence of a second argument. Note also that such a declaration only applies to the file where it is found (or to the scope in which it is found, if it's used inside a function).

  • 1
    Thanks. Re-reading the documentation of defvar makes it clear: "if INITVALUE is missing, SYMBOL's value is not set." I had missed this and always provided a second argument (which caused the variable to always be bound). – Francesco Sep 15 '12 at 9:54

Declare the variable with defvar. There is no other way to remove the warning, and it is really considered good practice.

Your intention to keep the symbol table uncluttered is worthy, but you are not actually doing so. I think you have misunderstood the semantics of variable bindings in Emacs Lisp, since you seem to believe that by not declaring it foo-state will be unbound in any buffer not using foo-mode. That is not the case.

In Emacs Lisp names (aka symbols) are global. As soon as foo-state is evaluated the first time, the runtime creates a new symbol object for foo-state and puts this into the global symbol table (aka obarray). There are no local symbol tables, so it does not matter where foo-state is evaluated and how, foo-state refers to the same symbol object at any place (see Creating Symbols).

Each symbol object consists of components (aka cells), one of which is the variable cell (see Symbol components). setq modifies the current binding of the system, on top-level without lexical binding this effectively changes the variable cell of the symbol object, thus the global value of the variable. Again, it does not matter where setq is evaluated. Actually if some bar-mode evaluated (setq foo-state "bar"), foo-state would be bound to "bar" in foo-mode too, and vice versa.

Thus the only effect of (defvar) over (setq) it that documents the intention of using a symbol as global variable, so telling others to not modify this variable unless manipulation of the behavior of foo-mode is intended. You can attach documentation to the variable, and mark as being defined in your buffer (C-h v foo-state will provide a link to jump to the definition).

Since Emacs Lisp lacks namespaces and is – by default – dynamically scoped, documentation is fundamentally important to avoid conflicts between modules. If I wrote a bar-mode using your foo-mode I might accidentally bind to foo-state, call foo-change-state and then see my mode misbehaving because a variable was unintentionally overwritten. Declaring foo-state doesn't make this impossible, but it at least allows me to catch the error, because C-h v foo-state will reveal that this variable is used by another mode, so I'd better not use it unless I really intend to manipulate that mode.

As a last word: In all of the aforementioned text “mode” can be replaced with Emacs Lisp files. modes are nothing special with regards to symbols. All of the above also holds for Emacs Lisp that do not declare modes, but just contain a bunch of functions.

  • 1
    It is not true that foo-state will not be unbound in other buffers — just try the poster's example. While the obarray is global, values of buffer-local symbols are changed on each buffer switch, which can include a change to unbound state. If you press M-: foo-state RET in a foo-mode buffer, you will get "bar", while if you do the same in another buffer, you get an error. While this is not idiomatic in elisp, it can certainly be obtained regardless of the global symbol table. – user4815162342 Sep 14 '12 at 23:48
  • Also note that, since Emacs Lisp lacks a module system, it's not the documentation that protects you from accidental clashes of global variables, but the convention of using symbol prefixes. This is why each mode prefixes all of its symbols with mode-. defvar merely allows the compiler to emit (more precisely, not to emit) the warning that catches misspelled assignments, the same warning that is a false positive in the poster's case. – user4815162342 Sep 14 '12 at 23:51
  • If foo-state is set by another buffer before foo-mode declares it buffer local, it gets a global value. Only after the variable is declared buffer local, buffer-local bindings come into effect. And even then the global binding can still be changed via setq-default, though this will of course not affect buffer local bindings. – lunaryorn Sep 15 '12 at 11:31
  • Symbol prefixes help to avoid conflicts, but do not prevent them, the more since especially variables declared by programming modes often have ubiquitous names simply because the the symbol prefix (i.e. the programming language's name) is ubiquitous. For instance, ruby-mode may declare a ruby-executable variable for buffer evaluation through a ruby interpreter, but any other library that deals with Ruby (i.e. a flymake checker for Ruby) might also easily use this variable, because it's just the most natural name to choose for this. That's why documentation is also needed… – lunaryorn Sep 15 '12 at 11:33
  • So in short, does it mean that in elisp, everything is just "global variables"? Modifying one place would result in all following executions to use the new value? Very nice explanation! – Hot.PxL May 24 '15 at 7:27

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.