The Many Faces of flet: cl-flet, cl-labels, and cl-letf
Way back in 2013 I wrote about the deprecation of
flet and how
noflet could fill the gap. Thirteen years later, it’s probably time
for a proper overview of what replaced flet in cl-lib and when to
use each option.
Emacs Lisp doesn’t have a built-in way to define local functions (the
way let defines local variables), so cl-lib provides several macros
for this. If you’ve ever been confused by cl-flet, cl-labels, and
cl-letf – you’re not alone. The naming doesn’t make the distinctions
obvious, and the documentation is a bit dry. Let’s try to fix that.
A Bit of History
The original flet (from the old cl package) let you temporarily
override a function’s definition. It worked by swapping out the
function’s symbol-function cell and restoring it when the body
finished – essentially a dynamic let for functions:
;; Old-style flet (deprecated since Emacs 24.3)
(flet ((some-function () "overridden result"))
;; Everything here, including called functions, sees the override
(some-function))
This was very handy for testing (stubbing impure functions), but it conflated two different things into one macro:
- Defining local helper functions (a lexical concept)
- Temporarily overriding a global function (a dynamic concept)
When cl was reorganized into cl-lib in Emacs 24.3, flet was
split into separate macros for each use case. This also brought the
lexical variants in line with Common Lisp semantics, where flet and
labels are lexically scoped.
The Three Replacements
cl-flet: Local Functions (No Recursion)
cl-flet binds function names lexically within its body. The key
thing to understand is that the binding is only visible in the body
forms – not inside the function’s own definition, and not to any
functions you call:
(cl-flet ((double (n) (* n 2)))
(double 21)) ; => 42
Because it’s lexical, cl-flet cannot override functions seen by
other code:
(defun my-helper () (+ 1 2))
(defun my-caller () (my-helper))
(cl-flet ((my-helper () 999))
(my-caller)) ; => 3, NOT 999!
my-caller still sees the original my-helper. This is the
fundamental difference from the old flet.
There’s also cl-flet*, which is to cl-flet what let* is to
let – each binding can reference the ones before it.
Use cl-flet when you just need a simple local helper and don’t need
recursion. Think of it as let for functions.
cl-labels: Local Functions (With Recursion)
cl-labels is like cl-flet, but the function is visible inside
its own body and inside the bodies of sibling bindings. This makes
recursion and mutual recursion possible:
(cl-labels ((factorial (n)
(if (<= n 1) 1
(* n (factorial (- n 1))))))
(factorial 10)) ; => 3628800
This would blow up with cl-flet because factorial wouldn’t be
defined inside its own body.
Mutual recursion works too:
(cl-labels ((my-even-p (n) (if (= n 0) t (my-odd-p (- n 1))))
(my-odd-p (n) (if (= n 0) nil (my-even-p (- n 1)))))
(list (my-even-p 4) (my-odd-p 3))) ; => (t t)
Use cl-labels when your local functions need to call themselves or
each other.
Note: cl-labels requires lexical-binding to be t in the file (which
it really should be for any modern Emacs Lisp code).
cl-letf: Temporary Global Override
This is the one that actually replaces the old flet’s dynamic
behavior. cl-letf temporarily rebinds a generalized place (anything
setf can handle) and restores it on exit:
(defun my-helper () (+ 1 2))
(defun my-caller () (my-helper))
(cl-letf (((symbol-function 'my-helper) (lambda () 999)))
(my-caller)) ; => 999
Now my-caller does see the override, because cl-letf modifies the
actual symbol-function cell of my-helper – just like the old flet did.
The original definition is restored when the body exits, even on error.
The syntax is a bit verbose because cl-letf isn’t specific to
functions – it’s a general-purpose temporary binding macro for any
setf-able place. (symbol-function 'name) is a “generalized
variable” that refers to the function stored in a symbol’s function
cell – it’s just one of many places cl-letf can bind. For example:
;; Temporarily silence messages
(cl-letf (((symbol-function 'message) #'ignore))
(do-something-noisy))
Use cl-letf when you need the old dynamic flet behavior – typically
for testing (stubbing functions) or temporarily suppressing/redirecting
behavior.
Quick Reference
| Scope | Recursion | Overrides global? | |
|---|---|---|---|
cl-flet |
Lexical | No | No |
cl-labels |
Lexical | Yes | No |
cl-letf |
Dynamic | N/A | Yes |
In other words:
- Default to
cl-fletfor local helpers. It’s the simplest and most predictable. - Reach for
cl-labelswhen you need recursion or mutual recursion in local functions. - Use
cl-letfonly when you genuinely need dynamic override – mainly in tests. Modifying global function cells is a sharp tool and it’s not thread-safe, so keep it contained.
That’s all I have for you today. Keep hacking!