defer and function modifiers
defer keeps cleanup next to the code that needs it, so it cannot be forgotten on an early return. The function modifiers tell the compiler how a function behaves, and some of them it checks for you.
defer
A statement written after defer does not run where it stands. It runs when the surrounding block ends. With several defers, the last one runs first, the way you undo steps in reverse order. The commonest use is releasing something right beside the line that acquired it.
func work():
println "starting work"
defer println "first cleanup"
defer println "second cleanup"
println "doing the work"
end
func main:
work()
println "after the work"
endEvery way out
A deferred statement runs whichever way the block is left: at its end, at a ret in the middle, or at a break out of a loop. That is the whole point. A function with several ret statements needs one defer, not a copy of the cleanup before each ret.
func find(items: int[], wanted: int): int:
defer println "search for", wanted, "finished"
each i, item in items:
if item == wanted:
ret i
end
end
ret -1
end
func main:
codes := [404, 200, 500]
println "position:", find(codes, 200)
println "position:", find(codes, 301)
endThe deferred line runs after the result has been worked out but before the caller gets it, so it is printed before position:. The same holds in main: a ret 1 still runs main's defers, which is why os.Exit is not allowed there.
defer belongs to its block
A defer runs at the end of the block it is written in, not at the end of the function. Inside a loop that means at the end of every round, and inside an if at the end of that branch.
func main:
repeat 3 in day:
defer println " close day", day
println "open day", day
end
late := 2
if late > 0:
defer println " report sent"
println "late orders:", late
end
println "done"
endSo something acquired inside a loop body and deferred there is released before the next round starts, which is usually exactly what you want.
When the values are read
A deferred statement reads its variables when it runs, not when the defer line is reached. If a variable changes in between, the defer sees the new value. To keep the value from the moment of the defer, copy it into another variable first.
func main:
mut stage := "start"
defer println "deferred sees:", stage
saved := stage
defer println "saved copy:", saved
stage = "finish"
println "stage now:", stage
endFreeing collections
A Vector or a HashMap takes memory that you give back with free(). Write defer x.free() on the line right after creating it, and every path out of the function frees it, including the early ret 0 here.
func average reading(raw: int[]): int:
mut valid := Vector {} as Vector<int>
defer valid.free()
each r in raw:
if r >= 0:
valid.push(r)
end
end
if valid.len() == 0:
ret 0
end
mut sum := 0
each v in valid:
sum += v
end
ret sum / valid.len()
end
func main:
println average reading([21, -1, 23, 25, -1])
println average reading([-1, -1])
endThe collections lesson covers Vector and HashMap, and the memory lesson explains what needs freeing and why.
Closing files
The same pattern closes files. Check that the open worked, then defer the close straight away; the code that follows may return early without leaving the file open. Here main also defers deleting the temporary file, so it is gone when the program ends.
import os
func save report(path: str, total: int): bool:
f := os.Open(path, "w")
if f == null:
ret false
end
defer f.close()
f.write("total: " + total + "\n")
f.write("status: paid\n")
ret true
end
func main:
path := os.TempDir() + "/salam-report.txt"
if not save report(path, 245):
printerrln "cannot write", path
ret 1
end
defer os.Remove(path)
print os.ReadFile(path)
endA defer may call a function that returns a value, as os.Remove does; the value is simply dropped. There is no block form defer: ... end. When the cleanup takes several steps, put them in a function and defer the call.
Function modifiers
These words go before func and say something about the function. Some are promises the compiler checks, others are hints:
| Modifier | Meaning | What the compiler does |
|---|---|---|
pure | No side effects: the result depends only on the arguments | Rejects printing, writing a global or calling an impure function inside it |
inline | Place the body at the call site | Takes it as a hint, useful for small functions called very often |
noinline | Keep it a real call | Takes it as a hint, handy when profiling or debugging |
noret | The function never returns, for example it ends the program | Treats code after a call to it as unreachable |
deprecated | The function is obsolete | Warns at every call (W013) |
pub | Visible outside its package | See the packages lesson |
pure func net price(price: int, discount: int): int:
ret price - price * discount / 100
end
inline func square(n: int): int:
ret n * n
end
noinline func receipt line(name: str, amount: int): str:
ret name + ": " + amount
end
func main:
println net price(200, 15), square(9)
println receipt line("tea", net price(90, 10))
endAdd a println to net price and the build stops with "'pure' function 'net price' cannot call 'println': it has side effects". That check is what makes pure worth writing: a pure function is safe to call anywhere, any number of times.
import os
noret func fail(message: str):
printerrln "error:", message
os.Exit(2)
end
deprecated func old total(a: int, b: int): int:
ret a + b
end
func check age(age: int): int:
if age < 0 or age > 150:
fail("impossible age " + age)
end
ret age
end
func main:
println check age(34)
println old total(2, 3)
endCalled with an age of -3, check age prints "error: impossible age -3" and the program exits with code 2. Because fail is noret, the compiler knows the call never comes back, and a statement written right after it is rejected as unreachable (E070). It does not check the body, though: a noret function that does return crashes the program, so make sure it always ends in os.Exit or another call that never returns. Calling old total builds and runs, with a W013 warning.
Rules of thumb
- Write the
deferon the line right after you acquire the thing it releases. - Defers run in reverse order, at the end of their own block.
- A deferred statement sees the values its variables have when it runs.
- Mark a function
purewhen it is, and let the compiler keep it that way. - Use
noretonly for functions that really end the program.