13

This was inspired by the discussion of lexical-binding vs lexical-let in this question. As lexical-binding gives you the ability to have useful closures people may be used to in other languages like JavaScript why wouldn't you enabled it all the time?

Assuming backwards compatibility with older Emacsen is not a concern what pitfalls should you be looking for if enabling it in legacy code buffers?

1 Answer 1

14

A major pitfall is that the binding semantics for undefined variables—i.e. variables not defined with defvar and friends—change with lexical-binding: Without it, let binds everything dynamically, but with lexical-binding enabled undefined variables are bound lexically, and even elided completely if unused in the current lexical scope.

Old code sometimes relies on this. To avoid hard dependencies for optional features, it'd bind dynamic variables without requiring the corresponding library or declaring the variable itself:

(let ((cook-eggs-enabled t))
  (cook-my-meal))

If the cooking feature is optional, we don't want to force unnecessary dependencies onto the user, so we don't use (require 'cook) and instead rely on autoloading of the cook-my-meal function.

It's obvious for the human reader that cook-eggs-enabled is not a local variable, but still refers to some global dynamic variable from the cook library here. Without lexical-binding this code works as intended: cook-eggs-enabled is bound dynamically, whether defined or not.

With lexical-binding however, it breaks: cook-eggs-enabled is now bound lexically (and then optimized away, because it's not used), so the global dynamic variable cook-eggs-enabled is not ever touched at all and still nil by the time cook-my-meal is called, so we surprisingly won't have any eggs in our meal.

Luckily, these issues are very easy to spot: The byte compiler naturally warns about an unused lexical binding here.

The fix is simple: Either add a (require 'cook) (for features that aren't really optional anyway), or—to avoid hard dependencies—declare the variable as dynamic variable in your own code. There is a special defvar form for this:

(defvar cook-eggs-enabled)

This defines cook-eggs-enabled as dynamic variable, but doesn't affect the docstring, the load-history (and thus find-variable and friends) or anything else, except the binding nature of the variable.

2
  • In the dynamic case, wouldn't that code snippet cause cook-eggs-enabled to be unbound when let finishes? I'm pretty sure I've run into a bug like this before. The defvar was happening inside the let, and the let later restored the variable to its initial (void) state.
    – Malabarba
    Dec 11, 2014 at 15:44
  • 1
    @Malababa No, that is a different situation. See the last paragraph of Defining Variables for the reason.
    – user227
    Dec 11, 2014 at 17:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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