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.