Skip to main content

Variables

Variables are declared with the let keyword. Every variable must be initialized at declaration.

let x = 42
let y = x + 1
let z = x * y - 10

Variable names must start with a letter or underscore and may contain letters, digits, and underscores.

Rules

  • A variable can hold any Jade value. See Types for the full list.
  • A variable may be referenced in any expression written after it.
  • Referencing an undeclared name is a compile-time error. jade check catches it before the program runs, so nothing before the mistake executes.
  • Variables declared inside a function body are local to that call and are not visible outside.

Blocks

A name first introduced inside an if, while, or for block cannot be used after the block ends:

if true {
let inner = 9
}
print(inner) // error: undefined variable 'inner'

A let that reuses a name from outside the block is a different case: it overwrites the outer variable rather than shadowing it, and the new value survives the block.

let x = 1
if true {
let x = 99
}
print(x) // 99, not 1

Pick a fresh name inside a block when you want the outer one left alone.

Statements end at the line break

There is no statement separator to write. A statement ends where its line does, and Jade fills in the break for you.

Jade does not accept a semicolon you type yourself — let x = 1; is a lexer error, not a harmless extra. Leave the line ending bare:

let x = 1
let y = 2

A line break inside () or [] does not end a statement, so a long call or array can span several lines.

Reassigning

Assignment without let changes a variable that already exists:

let count = 1
count = count + 1
print(count) // 2

A second let on the same name is allowed too, and simply rebinds it. Either way the new value need not be the same type as the old one — Jade infers a variable's type from what it currently holds rather than fixing it at declaration:

let value = "text"
value = 7
print(value) // 7

Decorators

A let may carry a decorator, which wraps the value in a function call:

fn shout(s) {
return s.upper()
}

@shout
let greeting = "hello" // same as: let greeting = shout("hello")

print(greeting) // HELLO

The point is not brevity — it is that the wrapper sits above the declaration instead of around it, so what the value actually is stays readable.

A decorator may take its own arguments. The decorated value goes first:

fn fence(s, tag) {
return f"<{tag}>{s}</{tag}>"
}

@fence("note")
let body = "keep it short" // same as: let body = fence("keep it short", "note")

Decorators stack, and the one written first is applied first:

@shout
@fence("p")
let loud = "hello" // fence(shout("hello"), "p") → <p>HELLO</p>

That is the same order fn decorators use, and the reverse of Python's.

A decorator can also be namespaced, using :: like an import:

@style::tagged
let body = "keep it short"

The same syntax works on a prompt declaration, which is where it earns its keep — see Prompts and Inference.