Defer¶
A defer always runs at the end of a scope at any point after it is declared, defer is commonly used to simplify code that needs clean-up; like closing unix file descriptors, freeing dynamically allocated memory or closing database connections.
End of a scope¶
The end of a scope also includes return, break, continue or rethrow !.
fn void test()
{
io::printn("print first");
defer io::printn("print third, on function return");
io::printn("print second");
return;
}
defer runs after the other print statements, at the function return. Note
Rethrow ! unwraps the Optional result if present, afterwards the previously Optional variable is a normal variable again, if the Optional result is empty then the Excuse is returned from the function back to the caller.
Defer Execution order¶
When there are multiple defer statements they are executed in reverse order of their declaration, last-to-first declared.
fn void test()
{
io::printn("print first");
defer io::printn("print third, defers execute in reverse order");
defer io::printn("print second, defers execute in reverse order");
return;
}
Example defer¶
import std::io;
fn char[]? file_read(String filename, char[] buffer)
{
// return Excuse if failed to open file
File file = file::open(filename, "r")!;
defer {
io::printn("File was found, close the file");
if (catch excuse = file.close())
{
io::printfn("Fault closing file: %s", excuse);
}
}
// return if fault reading the file into the buffer
file.read(buffer)!;
return buffer;
}
If the file named filename is found the function will read the content into a buffer, defer will then make sure that any open File handlers are closed. Note that if a scope exit happens before the defer declaration, the defer will not run. This is a useful property because if the file failed to open, we don't need to close it.
defer try¶
A defer try is called at end of a scope when the returned Optional contained a result value.
Examples¶
fn void? test()
{
defer try io::printn("✅ defer try run");
// Returned an Optional result
return;
}
fn void main(String[] args)
{
(void)test();
}
defer try runs on scope exit. fn void? test()
{
defer try io::printn("❌ defer try not run");
// Returned an Optional Excuse
return io::FILE_NOT_FOUND~;
}
fn void main(String[] args)
{
if (catch err = test())
{
io::printfn("test() returned a fault: %s", err);
}
}
defer try does not run on scope exit. defer catch¶
A defer catch is called at end of a scope when exiting with an Optional Excuse, and is helpful for logging, cleanup and freeing resources.
Memory allocation example¶
import std::core::mem;
fn char[]? test()
{
char[] data = mem::new_array(char, 12);
defer (catch err)
{
io::printfn("Excuse found: %s", err);
free(data);
}
// Returns Excuse, memory gets freed
if (!test_something(data)) return io::FILE_NOT_FOUND~;
// Returns data, defer catch doesn't run.
return data;
}
Pitfalls with defer and defer catch
If cleaning up memory allocations or resources make sure the defer or defer catch are declared as close to the resource declaration as possible. This helps to avoid unwanted memory leaks or unwanted resource usage from other code rethrowing ! before the defer catch was even declared.
fn void? function_throws()
{
return io::FILE_NOT_FOUND~;
}
fn String? test()
{
char[] data = mem::new_array(char, 12);
// ❌ Before the defer catch declaration
// memory was NOT freed
// function_throws()!;
defer (catch err)
{
io::printn("freeing memory");
free(data);
}
// ✅ After the defer catch declaration
// memory freed correctly
function_throws()!;
return (String)data;
}