Help: Defer loading an Emacs package using use-package fails

Watching Protesilaos’s use-package video tutorial I thought I finally got it, but I evidently did not.

I am trying to defer loading of multi-vterm until I press the C-c w keybinding, which fires kakafarm/multi-vterm-weechat. This kakafarm function uses the predicate function multi-vterm-buffer-exist-p, defined in the package multi-vterm, but it is not loaded when I press C-c w, and the kakafarm function fails with cond: Symbol’s function definition is void: multi-vterm-buffer-exist-p.

Mind you - when I press the other keybindings mentioned in the :bind section, e.g. C-c m m, multi-vterm runs and a new *vterminal* buffer appears.

The use-package:

(use-package multi-vterm
  :defer t
  :bind
  (
   :map global-map
   ("C-c w" . kakafarm/multi-vterm-weechat)
   ("C-c <RET> <RET>" . multi-vterm)
   ("C-c <RET> m" . multi-vterm)
   ("C-c m <RET>" . multi-vterm)
   ("C-c m m" . multi-vterm)
   )
  :commands
  (
   multi-vterm
   kakafarm/multi-vterm-weechat
   )
  )

The kakafarm function:

(defun kakafarm/multi-vterm-weechat ()
  "Either start a weechat vterm buffer, or switch to it if it already exists."

  (interactive)

  (let* ((vterm-shell (expand-file-name "~/bin/w"))
         (weechat-buffer-name "weechat")
         (maybe-weechat-buffer (get-buffer "weechat")))
    (cond
     ((multi-vterm-buffer-exist-p maybe-weechat-buffer)
      (switch-to-buffer maybe-weechat-buffer))
     (t
      (multi-vterm)
      (rename-buffer weechat-buffer-name)))))

Also, maybe the title isn’t the most descriptive. Not sure how to name it succinctly.

I assume that kakafarm/multi-vterm-weechat is defined within your init.el. Well, that’s an issue.

While use-package might seem like magic, it does not actually yield too much magic behind the scenes. Instead, it “just” adds fancy autoloads. We can check this by running M-x pp-macroexpand-last-sexp:

(progn
  (defvar use-package--warning25
    #'(lambda
        (keyword err)
        (let
            ((msg
              (format "%s/%s: %s" 'multi-vterm keyword
                      (error-message-string err))))
          (display-warning 'use-package msg :error))))
  (condition-case-unless-debug err
      (progn
        (unless
            (fboundp 'kakafarm/multi-vterm-weechat)                      ; (1)
          (autoload #'kakafarm/multi-vterm-weechat "multi-vterm" nil t)) ; (2)
        (unless
            (fboundp 'multi-vterm)
          (autoload #'multi-vterm "multi-vterm" nil t))
        (bind-keys :package multi-vterm :map global-map
                   ("C-c w" . kakafarm/multi-vterm-weechat)
                   ("C-c <RET> <RET>" . multi-vterm)
                   ("C-c <RET> m" . multi-vterm)
                   ("C-c m <RET>" . multi-vterm)
                   ("C-c m m" . multi-vterm)))
    (error
     (funcall use-package--warning25 :catch err))))

If I understood autoloads correctly, then an autoload is just a temporary function that will on its invocation load another file and then call the (now correctly loaded) function with the same name.

So with that, we either already have defined kakafarm/multi-vterm-weechat already at that point and do not add the autoload. Or we define kakafarm/multi-vterm-weechat later in the same init.el and therefore overwrite the autoload.

Either way, the :command and :bind are not correct: both should exclusively take commands from the package itself, not from other parts.

With that in mind, we come up with:

(use-package multi-vterm
  :init
  (bind-key "C-c " #'kakafarm/multi-vterm-weechat)
  :bind (("C-c <RET> <RET>" . multi-vterm)
         ("C-c <RET> m"     . multi-vterm)
         ("C-c m <RET>"     . multi-vterm)
         ("C-c m m"         . multi-vterm))
  :commands multi-vterm-buffer-exist-p)

Note that the kakafarm/... function is not used as either :commands, nor as :bind target. Also: as soon as you have :commands and/or :bind set, the :defer t is automagic.

Hope that helps!

1 Like

I fixed it by adding a (require 'multi-vterm) right under the (interactive) of kakafarm/multiv-term-weechat . Thank you!

(use-package multi-vterm
  :defer t
  :init
  (bind-key "C-c C-w" 'kakafarm/multi-vterm-weechat)
  (bind-key "C-c w" 'kakafarm/multi-vterm-weechat)
  :bind
  (
   :map global-map
   ("C-c <RET> <RET>" . multi-vterm)
   ("C-c m m"         . multi-vterm)
   )
  :commands
  (
   multi-vterm
   multi-vterm-buffer-exist-p
   )
  )
(defun kakafarm/multi-vterm-weechat ()
  "Either start a weechat vterm buffer, or switch to it if it already exists."

  (interactive)

  (require 'multi-vterm)

  (let* ((vterm-shell (expand-file-name "~/bin/w"))
         (weechat-buffer-name "weechat")
         (maybe-weechat-buffer (get-buffer "weechat")))
    (cond
     ((multi-vterm-buffer-exist-p maybe-weechat-buffer)
      (switch-to-buffer maybe-weechat-buffer))
     (t
      (multi-vterm)
      (rename-buffer weechat-buffer-name)))))

By the way, looking at the various unpacked use-package… they look very hairy.

Also, thanks for the align tip!

There’s one drawback with (require FEATURE). In order to check whether FEATURE is already loaded, Emacs looks into the features variable. That variable can grow quite fast, since several packages contain multiple features. Even emacs -q already has 121 features. I have a “somewhat” minimal configuration and I already have 434 features enabled.

The issue there is that (require FEATURE) uses (member FEATURE features), which is linear in its speed. Therefor, kakafarm/multi-vterm-weechat will be slower if multi-vterm is loaded early, as its entry in features will be pushed to the back.

The cleanest solution would still be an autoload, or even two of them: one autoload for your own package kakafarm, and one autoload for multi-vterm-buffer-exists-p.

I think in this case adding multi-vterm-buffer-exist-p to the commands which will trigger loading of the package will do the trick of loading it on first use.

I was burnt by varying variations on this theme when refactoring my config to use use-package. Hooks are similarly surprising in their effects.

What I concluded is that you essentially can only point things which already exist to things inside the package, if that makes any sense.

Okay, now fixed. I’ve (let ((vterm-shell …)) …) only in the case where a multi-vterm buffer already exist, which means that the multi-vterm package had already loaded and vterm-shell defined as a custom variable in multi-vterm’s loading.

Now I can defer both my kakafarm package and multi-vterm’s loading with :bind and :commands.

Many thanks!

(defun kakafarm/multi-vterm-weechat ()
  "Either start a weechat vterm buffer, or switch to it if it already exists."

  (interactive)

  (let* ((weechat-buffer-name "weechat")
         (maybe-weechat-buffer (get-buffer weechat-buffer-name)))
    (cond
     ((multi-vterm-buffer-exist-p maybe-weechat-buffer)
      (switch-to-buffer maybe-weechat-buffer))
     (t
      (let ((vterm-shell (expand-file-name "~/bin/w")))
        (multi-vterm)
        (rename-buffer weechat-buffer-name))))))

I previously thought the :commands section is only for commands, also known as interactive functions, not for all functions interactive and not interactive.

That’s already in my first post :slight_smile:

my bad : I think I need bigger glasses… :smiling_face: