known as "define"
special formsince v0.0-927 clojure.core/defEdit
(def symbol doc-string? init?)


Creates a global variable with the name of symbol and a namespace of the current namespace.

If init is supplied, it is evaluated and the result is assigned to symbol.

doc-string is an optional documentation string.

def is one of ClojureScript's special forms and is used by many macros to define common elements (ie: defn, defmacro, etc).

Supported metadata:

  • ^{:private boolean} - make non-accessible from other namespaces
  • ^{:dynamic boolean} - make dynamically bindable (usually named with *earmuffs*)
  • ^{:const boolean} - prevents redef and allows it to be used in case.
  • ^{:jsdoc ["..."]} - vector of JSDoc Tags for Google Closure or standard.
  • ^{:test (fn [] (assert ...))} - allows function to be tested with test.
  • ^{:doc "..."} - doc-string (prefer the use of the (def symbol doc-string init))
  • ^{:export boolean} - make this var publicly accessible from JS, using goog.exportSymbol

Compiler will also add metadata:

  • :ns
  • :name
  • :file
  • :line, :end-line
  • :column, :end-column
  • :source
  • :arglists


(def a)
;;=> nil

(def b 42)
;;=> 42

(def c "an optional docstring" 42)
;;=> 42

See Also:

Source docstring:
Creates and interns a global var with the name
of symbol in the current namespace (*ns*) or locates such a var if
it already exists.  If init is supplied, it is evaluated, and the
root binding of the var is set to the resulting value.  If init is
not supplied, the root binding of the var is unaffected.
Parser code @ clojurescript:src/main/clojure/cljs/analyzer.cljc
(defmethod parse 'def
  [op env form _ _]
  (when (> (count form) 4)
    (throw (error env "Too many arguments to def")))
  (let [pfn (fn
              ([_ sym] {:sym sym})
              ([_ sym init] {:sym sym :init init})
              ([_ sym doc init] {:sym sym :doc doc :init init}))
        args (apply pfn form)
        sym (:sym args)
        const? (-> sym meta :const)
        sym-meta (meta sym)
        tag (-> sym meta :tag)
        protocol (-> sym meta :protocol valid-proto)
        dynamic (-> sym meta :dynamic)
        ns-name (-> env :ns :name)
        locals (:locals env)
        clash-ns (symbol (str ns-name "." sym))
        sym-ns   (namespace sym)
        sym      (cond
                   (and sym-ns (not #?(:clj  (= (symbol sym-ns) ns-name)
                                       :cljs (symbol-identical? (symbol sym-ns) ns-name))))
                   (throw (error env (str "Can't def ns-qualified name in namespace " sym-ns)))

                   (some? sym-ns)
                   (symbol (name sym))

                   :else sym)]
    (when (some? (get-in @env/*compiler* [::namespaces clash-ns]))
      (warning :ns-var-clash env
        {:ns (symbol (str ns-name "." sym))
         :var (symbol (str ns-name) (str sym))}))
    (when (some? (:const (resolve-var (dissoc env :locals) sym)))
      (throw (error env "Can't redefine a constant")))
    (when-some [doc (:doc args)]
      (when-not (string? doc)
        (throw (error env "Too many arguments to def"))))
    (when (and (not dynamic)
               (earmuffed? sym)
               (not (core-ns? ns-name)))
      (warning :non-dynamic-earmuffed-var env
        {:var (str sym)}))
    (when-some [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])]
      (when (and (not *allow-redef*)
                 (not (:declared v))
                 (not (:declared sym-meta))
                 (get @*file-defs* sym))
        (warning :redef-in-file env {:sym sym :line (:line v)}))
      (when (and (:declared v)
                 (:arglists v)
                 (not= (:arglists v) (:arglists sym-meta)))
        (warning :declared-arglists-mismatch env {:ns-name  ns-name :sym sym
                                                  :declared (second (:arglists v))
                                                  :defined  (second (:arglists sym-meta))})))
    (let [env (if (or (and (not= ns-name 'cljs.core)
                           (core-name? env sym))
                      (some? (get-in @env/*compiler* [::namespaces ns-name :uses sym])))
                (let [ev (resolve-existing-var (dissoc env :locals)
                           ;; ::no-resolve true is to suppress "can't take value
                           ;; of macro warning" when sym resolves to a macro
                           (with-meta sym {::no-resolve true}))
                      conj-to-set (fnil conj #{})]
                  (when (public-name? (:ns ev) sym)
                    (warning :redef env {:sym sym :ns (:ns ev) :ns-name ns-name}))
                  (swap! env/*compiler* update-in [::namespaces ns-name :excludes]
                     conj-to-set sym)
                  (update-in env [:ns :excludes] conj-to-set sym))
          var-name (:name (resolve-var (dissoc env :locals) sym))
          init-expr (when (contains? args :init)
                      (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym]
                          {:name var-name}
                          (when (true? dynamic) {:dynamic true})
                          (source-info var-name env)))
                          (analyze (assoc env :context :expr) (:init args) sym))))
          fn-var? (and (some? init-expr) (= (:op init-expr) :fn))
          tag (cond
                fn-var? (or (:ret-tag init-expr) tag (:inferred-ret-tag init-expr))
                tag tag
                dynamic impl/ANY_SYM
                :else (:tag init-expr))
          export-as (when-let [export-val (-> sym meta :export)]
                      (if (= true export-val) var-name export-val))
          doc (or (:doc args) (-> sym meta :doc))]
      (when-some [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])]
        (when (and (not (-> sym meta :declared))
                   (and (true? (:fn-var v)) (not fn-var?)))
          (warning :fn-var env {:ns-name ns-name :sym sym})))

      ;; declare must not replace any analyzer data of an already def'd sym
      (when (or (nil? (get-in @env/*compiler* [::namespaces ns-name :defs sym]))
                (not (:declared sym-meta)))
        (when *file-defs*
          (swap! *file-defs* conj sym))

        (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym]
            {:name var-name}
            ;; remove actual test metadata, as it includes non-valid EDN and
            ;; cannot be present in analysis cached to disk - David
            (cond-> sym-meta
              (:test sym-meta) (assoc :test true))
            {:meta (-> sym-meta
                       (dissoc :test)
                       (update-in [:file]
                         (fn [f]
                           (if (= (-> env :ns :name) 'cljs.core)
            (when doc {:doc doc})
            (when const?
              (let [const-expr
                    (binding [*passes* (conj *passes* (replace-env-pass {:context :expr}))]
                      (analyze env (:init args)))]
                (when (constant-value? const-expr)
                  {:const-expr const-expr})))
            (when (true? dynamic) {:dynamic true})
            (source-info var-name env)
            ;; the protocol a protocol fn belongs to
            (when protocol
              {:protocol protocol})
            ;; symbol for reified protocol
            (when-let [protocol-symbol (-> sym meta :protocol-symbol)]
              {:protocol-symbol protocol-symbol
               :info (-> protocol-symbol meta :protocol-info)
               :impls #{}})
            (when fn-var?
              (let [params (map #(vec (map :name (:params %))) (:methods init-expr))]
                  {:fn-var (not (:macro sym-meta))
                   ;; protocol implementation context
                   :protocol-impl (:protocol-impl init-expr)
                   ;; inline protocol implementation context
                   :protocol-inline (:protocol-inline init-expr)}
                  (if-some [top-fn-meta (:top-fn sym-meta)]
                    {:variadic? (:variadic? init-expr)
                     :max-fixed-arity (:max-fixed-arity init-expr)
                     :method-params params
                     :arglists (:arglists sym-meta)
                     :arglists-meta (doall (map meta (:arglists sym-meta)))}))))
            (when (and (:declared sym-meta)
                       (:arglists sym-meta))
              {:declared true
               :fn-var true
               :method-params (second (:arglists sym-meta))})
            (if (and fn-var? (some? tag))
              {:ret-tag tag}
              (when tag {:tag tag})))))
        {:env env
         :op :def
         :form form
         :ns ns-name
         :name var-name
         :var (assoc
                  (-> env (dissoc :locals)
                    (assoc :context :expr)
                    (assoc :def-var true))
                :op :var)
         :doc doc
         :jsdoc (:jsdoc sym-meta)}
        (when-let [goog-type (:goog-define sym-meta)]
          {:goog-define goog-type})
        (when (true? (:def-emits-var env))
          {:var-ast (var-ast env sym)})
        (when-some [test (:test sym-meta)]
          {:test (analyze (assoc env :context :expr) test)})
        (when (some? tag)
          (if fn-var?
            {:ret-tag tag}
            {:tag tag}))
        (when (true? dynamic) {:dynamic true})
        (when (some? export-as) {:export export-as})
        (if (some? init-expr)
          {:init init-expr
           :children [:var :init]}
          {:children [:var]})))))

Emitting code @ clojurescript:src/main/clojure/cljs/compiler.cljc
(defmethod emit* :def
  [{:keys [name var init env doc goog-define jsdoc export test var-ast]}]
  ;; We only want to emit if an init is supplied, this is to avoid dead code
  ;; elimination issues. The REPL is the exception to this rule.
  (when (or init (:def-emits-var env))
    (let [mname (munge name)]
      (emit-comment env doc (concat
                              (when goog-define
                                [(str "@define {" goog-define "}")])
                              jsdoc (:jsdoc init)))
      (when (= :return (:context env))
        (emitln "return ("))
      (when (:def-emits-var env)
        (emitln "(function (){"))
      (emits var)
      (when init
        (emits " = "
          (if-let [define (get-define mname jsdoc)]
      (when (:def-emits-var env)
        (emitln "; return (")
        (emits (merge
                 {:op  :the-var
                  :env (assoc env :context :expr)}
        (emitln ");})()"))
      (when (= :return (:context env))
        (emitln ")"))
      ;; NOTE: JavaScriptCore does not like this under advanced compilation
      ;; this change was primarily for REPL interactions - David
      ;(emits " = (typeof " mname " != 'undefined') ? " mname " : undefined")
      (when-not (= :expr (:context env)) (emitln ";"))
      (when export
        (emitln "goog.exportSymbol('" (munge export) "', " mname ");"))
      (when (and ana/*load-tests* test)
        (when (= :expr (:context env))
          (emitln ";"))
        (emitln var ".cljs$lang$test = " test ";")))))