Posting answer to own question, including an example.
As @lawlist points out this can be done using
defvar-local, this example uses macros to avoid writing boiler plate code and prints a message if the function isn't defined, runs a fallback function.
(defmacro my-generic-fn-with-fallback (var description &rest body)
(defun ,var ()
(defvar-local ,var ,@body "Value storing a function, called by a function of the same name.")))
(defmacro my-generic-fn (var description)
(my-generic-fn-with-fallback ,var ,description
(message "Mode <%s> doesn't define <%s>, doing nothing!" major-mode ',var))))
(my-generic-fn my-generic-run "Run the current buffer")
(my-generic-fn my-generic-goto-thing-at-point "Go to symbol at the point")
(my-generic-fn my-generic-usage-of-thing-at-point "References to symbol point")
(my-generic-fn-with-fallback my-generic-jump-next "Jump forward"
(my-generic-fn-with-fallback my-generic-jump-prev "Jump backward"
(my-generic-fn-with-fallback my-generic-doc-jump-section "Jump to sections"
Then each mode can set functions:
;; --- snip
;; Generic functions
;; 'my-generic-run (no need)
(setq my-generic-goto-thing-at-point 'diff-goto-source-and-close)
(setq my-generic-jump-next 'diff-goto-source-and-close)
(setq my-generic-jump-prev 'kill-buffer-and-window)
;; Lambda's work too.
(setq my-generic-doc-jump-section (lambda () (interactive) (doc-jump-section-diff)))
;; --- snip
Bind the keys:
;; global key example.
(global-set-key (kbd "<f5>") (lambda () (interactive) (funcall my-generic-run)))
;; using evil-mode.
(define-key evil-normal-state-map (kbd "M-l") 'my-generic-jump-next)
(define-key evil-normal-state-map (kbd "M-h") 'my-generic-jump-prev)
(define-key evil-normal-state-map (kbd "M-RET") 'my-generic-goto-thing-at-point)
(define-key evil-normal-state-map (kbd "C-S-u") 'my-generic-usage-of-thing-at-point)