24

I would like a way to catch errors when running my init file, and then handle them gracefully. A lot of my most important customizations and keybindings show up at the end of my init file to make sure that other settings don't get applied over the top of them. The problem is that when initialization aborts early, I feel totally crippled trying to debug the problem without my familiar key bindings and settings being applied.

Is there any way to gracefully finish out the initialization process when an error occurs?

nispio
  • 8,175
  • 2
  • 35
  • 73

3 Answers3

10

Two options, neither of which are perfect, come to mind. First, you could wrap most of your early initialization code (ie, before it gets to your customizations) in (ignore-errors ...). If there are errors, however, there won't be a lot of feedback -- ignore-errors will simply return nil.

A more intricate option would be to wrap the potentially buggy code in a combination of unwind-protect and with-demoted-errors (with debug-on-error set to nil). The latter will error out gracefully-ish at the first error it encounters and report the error message to the *Messages* buffer for your inspection. Meanwhile, the rest of the unwind-protect body (presumably your customizations) will be evaluated. So, e.g.:

(unwind-protect
    (let ((debug-on-error nil))
      (with-demoted-errors
        (message "The world is about to end")
        (sleep-for 2)
        (/ 10 0)                        ; divide by zero signals an error
        (message "This will never evaluate")
        (sleep-for 2)
        (setq some-var 5)))
  (message "Here are the unwind forms that will always evaluate")
  (sleep-for 2)
  (setq some-var 10)
  (setq another-var "puppies")
  (message "All done!"))
Dan
  • 32,584
  • 6
  • 98
  • 168
9

@Dan described well how you can turn errors into messages. You can also do whatever you want with errors by using condition-case. Yet another option is to use unwind-protect.

I’ll stick to condition-case here, for no reason whatsoever.

Catching the Error

This should always guarantee your key definitions get evaluated, regardless of what happened inside condition-case. Any error gets stored inside init-error.

(defvar init-error nil 
  "The error which happened.")

(condition-case the-error
    (progn
      ;; Do the dangerous stuff here.
      (require 'what-I-want))
  (error
   ;; This is only evaluated if there's an error.
   (setq init-error the-error)))

;;; Do the safe stuff here.
(define-key uncrippling-map "\C-h" 'help!)

Throwing it Back

Afterwards, just throw the error again. There are several ways you can do that, here’s one.

;;; Throw the error again here.
(when init-error
  (funcall #'signal (car init-error) (cdr init-error)))
Malabarba
  • 22,878
  • 6
  • 78
  • 163
  • `unwind-protect` causes the error to be immediately re-raised, after executing whatever code you put in its rescue clause. It's like `finally` in a language such as Java, rather than `catch`. – sanityinc Oct 08 '14 at 19:47
3

The other answers have pretty well covered the low-level error-handling facilities that will be useful in a case like this. Another approach that can help is modularity. For instance, I divide my initialization file into several different files (using provide as appropriate), and I load them using this function instead of require:

(defun my/require-softly (feature &optional filename)
  "As `require', but instead of an error just print a message.

If there is an error, its message will be included in the message
printed.

Like `require', the return value will be FEATURE if the load was
successful (or unnecessary) and nil if not."
  (condition-case err
      (require feature filename) 
    (error (message "Error loading %s: \"%s\""
                    (if filename (format "%s (%s)" feature filename) feature)
                    (error-message-string err))
           nil)))

An error while loading a file in this way will still print a message, but it won't prevent execution of anything outside of the file where the error actually occurred.

Of course, this function isn't really that different from wrapping a require call in with-demoted-errors (I wrote it before I knew about with-demoted-errors), but the important point is that it you can essentially implement something like Dan's combination of with-demoted-errors and unwind-protect without wrapping (potentially very lengthy) blocks of code.

Aaron Harris
  • 2,664
  • 17
  • 22
  • This function was exactly what I was after. My emacs boots now despite reporting an error. After that, I just load my init file and `eval-buffer`. Thanks for posting it. – Kevin Aug 01 '16 at 03:08