Skip to content

Commit

Permalink
"Defer and cleanup" page (#27)
Browse files Browse the repository at this point in the history
- unified language to talk about faults, rather than a mix of error and fault
- clarified examples
- added in defer execution order example
  • Loading branch information
joshring authored Sep 3, 2024
1 parent 91e386f commit b7346c4
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .astro/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,13 @@ declare module 'astro:content' {
collection: "docs";
data: InferEntrySchema<"docs">
} & { render(): Render[".md"] };
"references/docs/defer.md": {
id: "references/docs/defer.md";
slug: "references/docs/defer";
body: string;
collection: "docs";
data: InferEntrySchema<"docs">
} & { render(): Render[".md"] };
"references/docs/define.md": {
id: "references/docs/define.md";
slug: "references/docs/define";
Expand Down
160 changes: 160 additions & 0 deletions src/content/docs/references/docs/defer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: Defer and Cleanup
description: Defer and Cleanup
sidebar:
order: 115
---

# Defer

A `defer` *always* runs at the [end of a scope](#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.

[Rethrow](../optionals/#rethrow) `!` unwraps the optional, making it a normal variable again if [successful](../optionals) or if unsuccessful returns the [fault](../optionals) from the function back to the caller.

```c3
fn void test()
{
io::printn("print first");
defer io::printn("print third, on function return");
io::printn("print second");
return;
}
```
The `defer` runs **after** the other print statments, at the function return.

### Defer Execution order
When there are multiple `defer` statements they are executed in reverse order of their declaration, last-to-first decalared.


```c3
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


```c3
import std::io;
fn char[]! file_read(String filename, char[] buffer)
{
File! file = file::open(filename, "r")!; // return if fault opening file
defer {
io::printn("File was found, close the file");
if (catch fault = file.close()) io::printfn("Fault closing file: %s", fault);
}
file.read(buffer)!; // return if fault reading the file into the 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 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](#end-of-a-scope) when exiting exiting with a [successful](../optionals) value.


### Examples

```c3
fn void test()
{
defer try io::printn("✅ defer try was run, a success was returned");
return;
}
fn void! main(String[] args)
{
test();
}
```
Function returns a [successful](../optionals) value, `defer try` runs on [scope exit](#end-of-a-scope).

```c3
fn void! test()
{
defer try io::printn("❌ defer try not run, a fault was returned");
return IoError.FILE_NOT_FOUND?;
}
fn void! main(String[] args)
{
if (catch fault = test()) {
io::printfn("test() returned a fault: %s", fault);
}
}
```
Function returns a [fault](../optionals), `defer try` does not run on [scope exit](#end-of-a-scope).



## Defer catch

A `defer catch` is called at [end of a scope](#end-of-a-scope) when exiting exiting with a [fault](../optionals), and is helpful for cleanup and freeing resources.


```c3
defer catch { ... }
```

```c3
defer (catch fault) { ... };
```
When the fault is captured this is convenient for logging the fault:
```c3
defer (catch fault) io::printfn("fault found: %s", fault)
```
### Memory allocation example


```c3
fn String! test()
{
char[] data = mem::new_array(char, 12)!;
defer (catch fault)
{
io::printfn("fault found: %s", fault)
(void)free(data);
}
return IoError.FILE_NOT_FOUND?; // returns fault, memory gets freed
}
```

## 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](../optionals/#rethrow) `!` before the `defer catch` declaration.

```c3
fn void! function_throws()
{
return IoError.FILE_NOT_FOUND?;
}
fn String! test()
{
char[] data = mem::new_array(char, 12)!;
// function_throws()!; // ❌ Before the defer catch declaration, memory was NOT freed
defer (catch err)
{
io::printn("freeing memory");
(void)free(data);
}
function_throws()!; // ✅ After the defer catch declaration, memory freed correctly
return (String)data;
}
```


0 comments on commit b7346c4

Please sign in to comment.