Errors, 3 of 4
Finally and Defer
Run cleanup whether a block succeeds or fails, with a Try's Finally or with Defer.
What you’ll build
A job runner that starts each job, runs it, and always cleans up after it, whether the job
succeeds, fails, or returns early. It does the cleanup two ways, with Finally and with Defer,
and shows when to use each.
Finally
A Try block’s Finally runs however the Try ends: after the body succeeds, after a Catch
handles an error, and when the body leaves through Return, Break, or an error that no Catch
takes.
Import std.core
Function Run(name: String, fail: Bool): String Throws Error
If fail Then Throw Error("{name} failed")
Return "{name} done"
End Function
For fail In [False, True]
Try
Print(Run("build", fail))
Catch e
Print("caught: {e.Message}")
Finally
Print("finally, after {fail ? "a failure" : "a success"}")
End Try
End Forbuild done finally, after a success caught: build failed finally, after a failure
A Finally can’t throw, or leave with Return, Break, or Continue, so it always finishes and
lets the Try end as it was going to.
Defer runs on errors too
A Defer runs when its block ends, and that includes ending with an error. The
cleanup sits right after the setup it undoes, and the Try that handles the error can be in
another function entirely.
Import std.core
Function Run(name: String, fail: Bool): String Throws Error
Print("start {name}")
Defer Print("clean up {name}")
If fail Then Throw Error("{name} failed")
Return "{name} done"
End Function
For fail In [False, True]
Try
Print(Run("test", fail))
Catch e
Print("caught: {e.Message}")
End Try
End Forstart test clean up test test done start test clean up test caught: test failed
The cleanup runs before the error reaches the Catch.
A Defer that could fail
A deferred statement can’t throw, since nothing would be there to catch its error. A cleanup call
that can fail uses Try?, which drops its error.
Import std.core
Function Release(name: String) Throws Error
If name == "" Then Throw Error("nothing to release")
Print("released {name}")
End Function
Function Work(name: String)
Defer Try? Release(name)
Print("working on '{name}'")
End Function
Work("cache")
Work("")working on 'cache' released cache working on ''
Which to use
Use Defer to undo something right where it’s set up: close what was opened, release what was
taken. It keeps the two together, and it runs whether or not a Try is around. Use Finally when
the cleanup belongs to the handling of the error itself, alongside the Catch.
Import std.core
Global running = 0
Function Run(name: String, fail: Bool) Throws Error
running++
Defer running--
If fail Then Throw Error("{name} failed")
Print("{name} ran, with {running} running")
End Function
Local failures = 0
For job In ["lint", "build", "deploy"]
Try
Run(job, job == "build")
Catch e
failures++
Finally
Print("after {job}: {running} running, {failures} failed")
End Try
End Forlint ran, with 1 running after lint: 0 running, 0 failed after build: 0 running, 1 failed deploy ran, with 1 running after deploy: 0 running, 1 failed
What you learned
Finallyruns however aTryends: success, a caught error,Return, or an error passing through.Deferruns when its block ends, through an error too, before aCatchfurther out.- A deferred call that can throw is written with
Try?. Deferkeeps cleanup next to its setup;Finallykeeps it with the error handling.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.