3

12.11.2 Creating and Deleting Buffer-Local Bindings:

Making a variable buffer-local within a let-binding for that variable does not work reliably, unless the buffer in which you do this is not current either on entry to or exit from the let.

So I test an example (in which the buffer is not current either on entry to or exit from the let) in the *scratch* buffer:

(setq number 0)

(with-current-buffer "*Messages*"
  (let (number)
    (with-current-buffer "*scratch*"
      (make-local-variable 'number)
      (setq number 1))))

number
;; ==> 0
;; Still, it returns the default value.

It seems that I did not understand what the manual said correctly.

I wonder what the manual mean by "work reliably",
can anyone give me an example that meets the conditions following "unless" and an opposite example that cannot "work reliably"?


Then the manual continued:

This is because let does not distinguish between different kinds of bindings; it knows only which variable the binding was made for.

But according to another example in 12.11.1 Introduction to Buffer-Local Variables:

(setq foo 'g)

(set-buffer "a")
(make-local-variable 'foo)
(setq foo 'a)

(let ((foo 'temp))
  (set-buffer "b")
  foo)
;; ==> g

let seems to distinguish ...


I know that making a variable buffer-local within a let-binding may not be a good coding convention, but since the manual mentioned it, I just want to figure it out.

1 Answer 1

1

The following works well.
After execution of the code the value of foo is 1 in all buffers except buffer *test2*, there the value is 3.
This is expected since in buffer *test2* the buffer-local value of foo was changed.

(unintern 'foo)
(defvar foo)
(setq foo 1)

(list
 (with-current-buffer (get-buffer-create "*test1*")
   (let ((foo 2))
     (with-current-buffer (get-buffer-create "*test2*")
       (make-variable-buffer-local 'foo)
       (setq foo 3)
       )
     foo
     )
   )
 (with-current-buffer "*test1*"
   foo)
 (with-current-buffer "*test2*"
   foo)
 foo
 )
;; => (2 1 3 1)

In the following example the local let-binding of foo leaks out of the let form.
In buffer *test1* the variable foo is locally let-bound and set to 3 within the let-body.
Outside the let-body it should have back its old value 1 in buffer *test1* but it is still has the value 3.

(unintern 'foo)
(defvar foo)
(setq foo 1)

(list
 (with-current-buffer (get-buffer-create "*test1*")
   (let ((foo 2))
     (make-variable-buffer-local 'foo)
     (setq foo 3)
     foo)
   )

 (with-current-buffer "*test1*"
   foo)
 (with-current-buffer (get-buffer-create "*test2*")
   foo)
 )
;; => (3 3 1)

An important aspect mentioned in Drew's comment:
Let-binding is not buffer local if the locally bound variable is not already declared as buffer-local.
Example:

(unintern 'foo)
(defvar foo 0)

(with-current-buffer (get-buffer-create "*test1*")
  (let ((foo 1))
    (with-current-buffer (get-buffer-create "*test2*")
      foo)))

But, if a variable is already declared as buffer-local a let-binding of this variable is also buffer-local:

(unintern 'foo)
(defvar-local foo 0)

(defvar ans)
(setq ans nil)

(with-current-buffer (get-buffer-create "*test1*")
  (setq foo 1)
  (let ((foo 2))
    (with-current-buffer (get-buffer-create "*test2*")
      (push foo ans)
      )
    (push foo ans)
    )
  (push foo ans)
  )
(nreverse ans)
;; => (0 2 1)
8
  • Why are the results different if I remove the forms (defvar foo)?
    – shynur
    Mar 19, 2023 at 12:46
  • @Shynur: If you remove the defvars then foo is bound lexically, assuming lexical-binding is non-nil.
    – Drew
    Mar 19, 2023 at 15:21
  • This part of the doc of make-variable-buffer-local might be relevant here: "Note that binding the variable with let', or setting it while a let'-style binding made in this buffer is in effect, does not make the variable buffer-local."
    – Drew
    Mar 19, 2023 at 15:26
  • @Drew You mentioned that let-binding does not make variables buffer-local. Right you are. This would destroy one important use-case of let-bindings: The transfer of values of buffer-local variables from one buffer to another. Org-mode uses this for the duplication of org-buffers when exporting. The buffer modifications through code block execution during export can easily be dismissed through killing the duplicated buffer.
    – Tobias
    Mar 19, 2023 at 15:35
  • @Shynur It is good praxis to always declare global variables of packages as special. One often needs to let-bind them in functions to avoid strange effects through customization or to achieve some special behavior.
    – Tobias
    Mar 19, 2023 at 15:41

Your Answer

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

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