Home About Documentation Templates Examples Showcase GitHub
Theme

Core language · State & bindings

Declarations & assignments.

Nift v4.1 introduced explicit lexical declarations and assignment. Use := to create a binding and = to update an existing mutable binding; the type inferred at declaration remains stable.

:= declares; = assigns.

They are deliberately different operations. Assignment never creates a missing name, which catches typos and makes the lifetime of state visible in the source.

Declare and assign

$[count := 0]
$[count = count + 1]
$[price := 19.95]
$[title := "Nift"]
$[published := true]

A top-level declaration or assignment inside $[...] changes state and renders no text. Reading the binding later renders its value normally:

$[visits := 1]
$[visits = visits + 1]
<p>Visits: $[visits]</p>

Stable inferred types

The declaration establishes the binding's type. Reassignment must preserve it.

$[count := 0]
$[count = 12]          
$[count = "twelve"]    

Numeric literal typing is lexical. An integer spelling (0, 8) infers int; a fractional or exponent spelling (0.0, 8.0, 8.5, 1e3) infers double, even when the value is integral. The inferred type then stays stable.

$[n := 0]
$[n = 8]
$[f := 0.0]
$[f = 8.5]
$[f = 3]

The .0 spelling makes 0.0 a floating-point literal, so f is double: reassigning 8.5 is valid while reassigning the integer 3 is an error.

An arithmetic expression is double when any operand is double, so a double accumulator remains double even when it lands on an integral value:

$[total := 0.0]
$[total = total + 8.5]
$[total = total + 9.0]

Arrays, objects and struct instances likewise retain their declared kind. Struct fields have the same stable inferred-type rule.

Lexical scope

Nested constructs run in child scopes. They can read and update an existing outer mutable binding, while a declaration made inside the child disappears when that child exits.

$[visible := 0]

@if(show) {
    $[visible = visible + 1]
    $[temporary := "inside only"]
}

$[visible]      

const and immut

$[const site_name := "Nift"]
$[immut config := {"features":["docs","builds"]}]

const prevents rebinding. immut establishes a recursively read-only binding/view contract. An immut view does not make separately reachable mutable storage globally immutable.

Arrays (lists)

Nift calls list-like values arrays, following JSON terminology. JSON arrays existed as data values before v4.1; v4.1 made array and object literals directly declarable in the expression language.

$[names := ["Ada", "Grace", "Linus"]]
$[matrix := [[1, 2], [3, 4]]]
$[users := [{"name":"Ada"}, {"name":"Grace"}]]

In v4.3 array elements are full expressions, evaluated from left to right exactly once:

$[x := 10]
$[i := 0]
$[computed := [x, x + 1, i++, i++, "42".to_int()]]

Use zero-based indexing to read an item and @for to iterate:

<p>First: $[names[0]]</p>

<ul>
@for(name : names) {
    <li>$[name]</li>
}
</ul>

Nift 4.3 arrays are mutable list values. They support push, pop, insert, remove, clear, first, last, indexOf, contains, size and empty.

$[names := ["Ada", "Grace"]]
$[names.push("Linus")]
$[names.insert(1, "Margaret")]
$[names.contains("Grace")]
$[names.indexOf("Linus")]
$[last := names.pop()]

Array search uses structural equality, so nested data values can be found by content. Mutating methods require a mutable binding; const/immut state remains protected. Higher-order transforms such as map, filter and reduce are documented on the collection operations page.

Objects

$[site := {"title":"Example", "draft":false}]
$[sites := [{"title":"One"}, {"title":"Two"}]]

$[site.title]
$[sites[1].title]

Objects and arrays can be nested freely. Ordinary JSON object/array values are not structs: use structs when you need fixed-shape state with methods, privacy and reference semantics.

Reference semantics (locations)

:= assigns a root binding. Assigning one aggregate binding to another, or extracting a nested aggregate, creates a location reference: the target names a location, not a frozen snapshot of the value that happened to be there.

$[a := [[1], [2]]]
$[b := a]        
$[c := a[0]]     
The location rule.

A nested reference identifies a location as root binding + normalized path. It does not permanently identify the aggregate that happened to occupy that location when the reference was created.

Structural mutation changes what a retained reference resolves to, so the reference always reflects the live location:

$[a := [[1], [2]]]
$[b := a[0]]
$[a.remove(0)]          
$[b]                    
$[a := [[1], [2]]]
$[b := a[0]]
$[a.insert(0, [9])]     
$[b[0]]                 
$[a := [[1]]]
$[b := a[0]]
$[a[0] = [99]]          
$[b[0]]                 

Rebinding the root, composing paths, and writes through a reference all follow the same rule. A reference into a location that no longer exists fails safely with reference target no longer exists; if a value later occupies that location again, the reference resumes resolving:

$[a := [[1]]]
$[b := a[0]]
$[a = [[7]]]            
$[b[0]]                 
$[a.clear()]
$[b]                    
$[a.push([3])]
$[b[0]]                 

Independent values with copy()

When you want an independent recursive copy rather than a location, use copy():

$[a := [[1], [2]]]
$[b := copy(a)]
$[b[0][0] = 99]
$[a[0][0]]              

Identity with same()

same(x, y) reports whether two aggregate references name the same location (same root binding identity and same normalized path). It is not structural equality (==) and not value equality:

$[a := [[1], [2]]]
$[b := a[0]]
$[c := a[0]]
$[same(b, c)]           
$[same(b, a[1])]        
$[d := copy(a[0])]
$[same(b, d)]           
$[a[0] == a[0]]         

Structs and collections are identity-bearing handles, so same() on them compares handle identity while == compares handle value equality.

Multiline declarations

@:=(config){
    {
        "title": "Example",
        "navigation": ["Home", "Docs", "About"]
    }
}

@:=(name){...} is the multiline spelling of $[name := expression]. The outer braces frame one expression; they are not part of the value.

Chained declaration and assignment

:= and = are right-associative, so related state can be initialized or updated in one expression when that improves clarity.

$[a := b := 0]
$[a = b = 5]

Prefer separate lines when the chain would make scope or intent harder to read.

Destructuring

Flat array and object destructuring is available with both := and =. Array patterns are exact-length; object patterns require named keys and ignore extras. See Types, optionality, ranges & enums.