# Set and Def
`set` and `def` both put a value under a name, and both evaluate to the
value they bound. They differ in which scope they write:
| | `set` | `def` |
|---|-------|-------|
| At module scope | Writes the module namespace | Writes the module namespace |
| Inside a function | Creates or rebinds a local | Writes the module namespace |
| Named functions | Binds a closure like any value | `def name {\|params\| ...}` declares one |
| Idiomatic use | Loop counters, accumulators, scratch state | Module constants, config, shared state, functions |
The one rule to remember: `def` always targets the module namespace;
`set` targets a local when one is in scope. At module scope the two
coincide — both write the namespace — which is why scripts use either at
the top level. The distinction only shows up inside functions and
closures.
## Module Scope — `def`
Module-level bindings are visible to every function and closure in the
file, and to importers of the module. Use `def` for declarations:
def base-url "https://api.example.com" def retry-limit 3 def url-for {|path| [str $base-url $path]}
`def` is also the syntax for declaring named functions:
def greet {|name| [str "hello " $name]}
Declarations are not hoisted: the `def` must run before the name is
used.
[greet "Alice"] # error — greet is not defined yet def greet {|name| ...}
At the top level, `set` writes the same module namespace, so this also
works — but the convention for module declarations is `def`:
set max-runs 3 # works at top level — writes the namespace def retry-limit 3 # preferred for module declarations
## Local Scope — `set`
Inside a function, `set` rebinds an existing local or creates a new one
on first use. This is the form for loop counters, accumulators, and
scratch space:
def total {|prices| set sum 0 for price in $prices { set sum ($sum + $price) } $sum }
Locals are captured by reference, so a closure that `set`s an enclosing
local mutates the original:
def make-counter {|| set n 0 {|| set n ($n + 1) $n} } def bump [make-counter] [bump] # => 1 [bump] # => 2
For unit increments, prefer `incr` over `set n ($n + 1)` (see
Syntax and Style).
## `def` Inside a Function Writes the Module
A value `def` inside a function does not create a local. It writes
the module namespace, exactly as if it appeared at the top level:
def ready false def init {|| def ready true # updates the module binding } [init] $ready # => true
This is how callbacks update shared module state — declare the slot at
module scope, then bind it with `def` inside the callback:
def last-event nil def on-event {|ev| def last-event $ev } def start {|| [pub/subscribe "events" "on-event"] } [start] # ... when an event arrives, the module binding updates
`set` in the same position would create a closure-local and leave the
module binding untouched:
def status "idle" def run {|| set status "busy" # new local — module $status stays "idle" $status }
If the module value is a collection, `<<` mutates the shared binding
from any closure (bare form, no `set` needed):
def seen @{} def watch {|| @{ 1 2 3 } |> each { $seen << $it } }
## Binding Cells — `[var x]`
Every module binding is backed by a mutable cell. `[var x]` returns that
cell itself (a ref cell), without dereferencing — `[var x]` is the bare
symbol only; any other call shape is a normal function call, so a user
function named `var` still works.
def ready false [var? [var ready]] # => true [deref [var ready]] # => false [reset! [var ready] true] # explicit mutation, returns true $ready # => true
The cell prints as the binding — `[var ready]` renders `#<var ready>`,
not the value (`$ready` / `[deref [var ready]]` are the value). Anonymous
cells render `#<ref …>`.
The cell is what makes module state watchable:
def last-event nil [add-watch [var last-event] :log {|key cell old new| echo "last-event:" $old "->" $new }] [reset! [var last-event] "click"] # prints: last-event: nil -> click [swap! [var last-event] {|_| "keydown"}]
Clojure semantics apply: watchers fire on `reset!`/`swap!` through the
cell, but not when `def` rebinds the name (the root write is silent).
The cell's identity survives redefinition, so a watcher registered once
keeps observing explicit mutations:
def last-event nil [add-watch [var last-event] :log {|key cell old new| ...}] def last-event "resize" # no notification [reset! [var last-event] "scroll"] # notification fires
`[var x]` resolves namespace scope only — locals are not cells, and
names with no interned cell (raw natives) raise. See
Refs for `deref`, `reset!`, `swap!`, and the
watch functions.
## Choosing
def config %{ :host "localhost" :port 8080 } # module state def handler {|req res| ... } # named function def process {|items| set count 0 # local counter for item in $items { incr count } $count }
- Module constants, configuration, shared state, functions: `def`.
- Local counters, accumulators, temporaries: `set`.
- Rebinding module state from inside a function or callback: `def`.
- Making a value visible only to the enclosing function and its
closures: `set`.
## See Also
- Syntax and Style — assignment, appending, and
captured locals.
- Functions — `def` forms, closures, multi-arity, and
captured locals.
- Modules and Imports — how module bindings are
exported.
- Metaprogramming — `set`/`def` in the special-forms
table.