Closures in Wurst are a way to make your code more powerful and flexible. They let you treat functions like variables, which means you can pass them around, save them for later, and even create them on the fly. This tutorial will explain closures step-by-step in a simple, beginner-friendly way.
A closure is a function that remembers the variables around it. Think of it like a tool that keeps everything it needs in its toolbox, even if you move it somewhere else.
In basic Jass, you can only store functions in a very limited way, using the code
type. You can’t:
Closures solve these problems, letting you use functions in flexible and useful ways.
Closures often use anonymous functions, which are functions without names. Let’s start by turning a regular function into an anonymous one. You’ll also learn how to use lambdas, a shortcut syntax for anonymous functions.
function greet()
print("Hello!")
init
CreateTrigger().addAction(function greet)
This passes the greet
function to addAction
. Here’s how to do the same thing with an anonymous function:
init
CreateTrigger().addAction(() -> print("Hello!"))
What’s happening here?
() ->
is the syntax for a lambda expression, which is a shorthand way to write anonymous functions.print("Hello!")
) comes after the arrow.By skipping the step of naming the function, your code becomes shorter and easier to read.
While the code
type does not support it, anonymous functions can take parameters just like regular functions. Example from ClosureForGroups
:
forUnitsInRange(ZERO2, 100, (u) -> print(u.getName()))
Key points:
(u) ->
replaces a named function.u
referencing that unit.The function forUnitsInRange
iterates over all units in the given range. In this example, the closure is used to print the name of each unit within 100 units of the ZERO2
location.
Closures take anonymous functions a step further. They can “remember” variables from their surroundings and use them later.
Let’s say you want a spell to deal damage after a short delay. Here’s how closures can make this easy:
function castSpell()
let caster = GetSpellAbilityUnit()
let target = getSpellTargetPos()
doAfter(0.5) ->
createEffect("Abilities\Spells\Meteor\Impact.mdl", target)
caster.damageTarget(target, 100)
How it works:
doAfter(0.5)
schedules the anonymous function to run after half a second.caster
and target
, so they’re available even when the function runs later.Closures copy the values of variables they reference at the time they are created. Think of it like taking a snapshot of the variables - any changes made to the variables inside the closure won’t affect it outside the closure, and vice versa.
For example, this code won’t behave as expected:
init
var i = 1
execute() ->
i = 2
print(i) // This will print "2"
print(i) // This will print "1"
Here, the second print uses the value of i
as it was initially. Setting it inside the closure only has an effect in the closure’s context. To share changes, you can use a Reference<T>
to wrap the variable:
init
var i = new Reference(1)
execute() ->
i.val = 2
print(i.val) // This will print "2"
print(i.val) // This will print "2"
destroy i
By using Reference<T>
, both the closure and the rest of your code can work with the same underlying value.
Sometimes, you’ll want to save a closure and use it multiple times. Closures can be stored in variables or lists just like other values.
interface Fn
function run()
function saveClosureExample()
Fn greet = () -> print("Hello, world!")
greet.run() // Call the stored closure
You can also save multiple closures in a list to call them later:
interface Fn
function run()
function storeClosuresInList()
let callbacks = new LinkedList<Fn>()
// Add some closures
callbacks.add(() -> print("Callback 1 executed!"))
callbacks.add(() -> print("Callback 2 executed!"))
// Execute them later
for callback in callbacks
callback.run()
Closures stored in a list can be executed sequentially or dynamically based on conditions, making them ideal for callback systems.
Closures can replace triggers for many tasks. Instead of writing a separate function for each trigger action, closures let you define the behavior directly.
Here’s an example of a simple custom event system using closures:
package CustomEvents
interface Listener
function run()
class EventSystem
private LinkedList<Listener> listeners = new LinkedList<>()
function addListener(Listener listener)
listeners.add(listener)
function triggerEvent()
for listener in listeners
listener.run()
init
let eventSystem = new EventSystem()
eventSystem.addListener(() -> print("Event triggered: Listener 1"))
eventSystem.addListener(() -> print("Event triggered: Listener 2"))
eventSystem.triggerEvent()
This example shows how closures simplify event handling by avoiding boilerplate code for each listener.
ClosureTimers
for more examples and inspiration.Closures in Wurst are powerful but easy to use once you get the hang of them. They’re great for managing actions, storing functions, and writing clean, reusable code. Start experimenting with closures in your projects and see how much they simplify your workflow!
For more details, visit the Wurst manual or join the community for support.
Happy coding!