SCUA

Manual

Variables: let and const

You bind a name to a value with let or const. A let can be reassigned; a const cannot. Both are block-scoped, and both can be declared at the top level of a file where functions in the same file can see them.

let score = 10
score = score + 5
print(score)

const MAX_HP = 100
print(MAX_HP)

Run it:

$ scua basics.scua
15
100

#let is reassignable

A let binding can be assigned a new value as many times as you like. Assignment uses the name on its own, without let:

let score = 10
score = score + 5

Use const for a value that should never change after it's set. By convention SCUA programs name constants in upper case, but that's a habit, not a rule.

#const is immutable

Reassigning a const is a compile error, caught before the program runs:

const MAX_HP = 100
MAX_HP = 120

Run it:

$ scua const-error.scua
scua: const-error.scua:2: cannot assign to const 'MAX_HP'

The error names the line and the binding. Note that const fixes the binding, not the contents of what it points to. A const that holds a table still lets you change the table's fields; what you can't do is point the name at a different value.

#Block scope

A binding is visible from where it's declared to the end of the block that contains it. A do ... end block is the simplest way to introduce a scope:

let x = 1
do
  let x = 2
  print(`inside: {x}`)
end
print(`outside: {x}`)

Run it:

$ scua scope.scua
inside: 2
outside: 1

The inner let x is a separate binding that lives only inside the block. When the block ends, the outer x is what's in scope again. The bodies of if, while, for, and functions are blocks too, so a binding made inside one of them is not visible outside it.

#Module-level bindings are visible inside functions

A let or const written at the top level of a file is in scope for every function defined in that same file. A top-level let is shared mutable state: a function can read it and update it, and the change persists across calls. A top-level const is read-only everywhere.

const STEP = 5
let score = 0

fn add_points()
  score = score + STEP
end

print(`start: {score}`)
add_points()
add_points()
print(`after two: {score}`)

Run it:

$ scua module-state.scua
start: 0
after two: 10

add_points reads the constant STEP and updates the shared score. Each call adds to the same binding, so after two calls score is 10. This is handy for a counter or a small piece of configuration that several functions share.

#Shadowing

You can declare a new binding with the same name as one already in scope. The new binding shadows the old one from that point on:

let value = "first"
let value = "second"
print(value)

Run it:

$ scua shadow.scua
second

This is a new binding, not an assignment, so it works even when the original was a const. It's most useful when you want to refine a value through a few steps and don't need the earlier versions, for example reassigning a name to a trimmed or parsed form of itself.

For how functions capture the variables around them, see Functions and closures.