GameLisp's variables and collections are mutable by default. This is convenient, but it can also
be nerve-wracking. It's sometimes difficult to know whether or not you have exclusive ownership of
a collection, so there's always the risk that typing
(push! ar x) or
(clear! coll) could
have unintended side-effects in some distant part of your codebase.
freeze! function takes any number of GameLisp values as arguments.
For each value which refers to an array, string, table, or object, a flag is set on that
collection which prevents it from being mutated. Future calls to
(= [ob 'x] 10),
(swap-remove! ar 5) and so on will trigger an error. Frozen collections can never be unfrozen,
and they can't even be mutated using the Rust API.
freeze! is not recursive: you can always mutate a non-frozen collection, even if you're
accessing it via a frozen collection. To freeze a value and all of the values which it
transitively refers to, use
Finally, you'll sometimes want the security of knowing that a global variable can't be
accidentally mutated. You can enforce this by calling
(GameLisp automatically freezes a few of the built-in functions, such as
+, so that they can be
optimized more aggressively.)
(def ar (arr (arr 'a 0) (arr 'b 1))) (push! ar (arr 'c 2)) (prn ar) ; prints ((a 0) (b 1) (c 2)) (freeze-global! 'ar) ;(= ar '()) ; this would be an error (freeze! ar) ;(push! ar (arr 'd 3)) ; this would be an error (push! [ar 0] 0) (prn ar) ; prints ((a 0 0) (b 1) (c 2)) (deep-freeze! ar) ;(push! [ar 1] 1) ; this would be an error
quote or self-evaluating types, it's possible to create multiple aliasing references to
data which was originally passed in to the evaluator. For example:
(loop (let ar ()) (push! ar 1) (prn ar))
Intuitively, you might expect
ar to be a fresh, empty array each time it's initialized, so that
prn call prints
(1) every time. Unfortunately, because empty arrays are self-evaluating,
() form will repeatedly return the same array. This loop would print
(1 1 1)...
In order to prevent this, whenever GameLisp encounters a
quoted or self-evaluating array, string
or table, if it's not already deep-frozen then it will be replaced with a
deep-cloned, deep-frozen copy of itself. This means that
self-evaluating forms and
quoted forms are always immutable, avoiding problems like the above.
To make unintended mutation even less likely, all data produced by the parser is automatically frozen. You can clone the data if you need a mutable copy.
clone function receives a single value as its argument. If this value is
an array, string, table, or iterator, it returns a shallow copy of that value. The copy will be
mutable, even if the original collection was frozen.
deep-clone function shallow-clones its argument, and then recurses
through to clone any collections referred to by the argument, and any collections referred to
by those collections, and so on.
Cloning an iterator will never clone the array, string or table which is being iterated, even when
deep-clone. When they encounter an iterator, both
deep-clone perform just
enough copying to ensure that
iter-next! will not modify the original
In GameLisp, the
== function specifically tests for numeric equality. It
exists to complement the other numeric comparison functions:
>. It's an
error to pass a non-numeric value to
same? is usually more strict than you'd like. For example,
(same? '(1 2) (arr 1 2)) will
#f, because it sees its arguments as being two distinct arrays, even though they have
You'll generally want to use the
eq? function instead. It differs from
in that arrays, strings and tables are deeply inspected: they compare equal when their contents
are recursively identical.
The final built-in equality test is
keys-eqv?, which was
eq? are variadic.
(== a b c d) tests whether
== to one another.
(ensure (eq-any? (coro-state c) 'newborn 'running 'paused 'finished 'poisoned)) (when (same-any? current-room graveyard dungeon prison-cell) (inc! fear-level))
Because the Rust standard library only has limited facilities for handling time, the same is true for GameLisp.
time function returns a high-precision monotonic timestamp measured in
seconds, and the
sleep function suspends the current thread for a specified
number of seconds. Note that although
time should have excellent precision on all platforms,
sleep is often very imprecise, particularly on Windows. You should prefer to time your main
loop using an external signal, such as blocking on VSync.
The only date-and-time facility provided by GameLisp is the
function, which returns the number of elapsed whole seconds in the
UNIX epoch. In order to avoid the 2038 problem it returns a string, such as
It's intended to be used as a basic timestamp for logging - it can't be readily converted into a
fn1 macros provide a concise way to define functions
with zero or one arguments.
fn1 can use a single underscore,
_, to refer to its argument.
fn1 form, you should try to avoid discarding a value in a pattern (e.g.
(match ... (_ x))), since it can be visually confusing.
(ensure (all? (fn1 (eq? _ 'red)) colors) "non-red color detected") ; ...is equivalent to... (ensure (all? (fn (color) (eq? color 'red)) colors) "non-red color detected")
In Section 2, we'll discuss how to invoke and tune the garbage collector
from Rust. You might find that you prefer to do that from within GameLisp, in which case you
can use the functions
You may have noted the conspicuous absence of an input/output (IO) library. GameLisp doesn't
provide any built-in functions for filesystem access, beyond
This is a deliberate design choice. IO is a huge topic: a full-featured IO library for game development would need to include byte streams, compression, string streams, stdio, text encodings, networking, non-blocking io, sandboxing, logging... the list goes on.
Meanwhile, your game engine will almost certainly have its own opinions about how filesystem and network access should be done. Something as simple as storing your game's assets in a .zip file might make the entire IO library unusable!
Instead, you're encouraged to use the Rust API to bind your engine's existing APIs to GameLisp.