# 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.