brago

package module
v0.0.0-...-f201d8a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2024 License: BSD-3-Clause Imports: 2 Imported by: 0

README

brago - Bracket Pattern in Go

Manage resources in Go using "Bracket Pattern" in a type-safe manner, without the footguns of defer.

Documentation

https://pkg.go.dev/github.com/thelissimus/brago

LICENSE

BSD-3-Clause

Documentation

Overview

Manage resources in Go using "Bracket Pattern".

What?

The bracket pattern is an alternative resource management abstraction to the defer keyword. It provides means to manage the resources:

  • Manually: Acquire manually, release manually.
  • Semi-automatically: Acquire manually, release automatically.
  • Automatically: Acquire automatically, release automatically.

Why?

More type safe than manual resource management with defer. You won't forget to close the resource or shoot yourself in the foot with the deferred call of Close on a nil pointer.

Let's look at examples.

Footgun 1: Forgetting to close the resource.

f, err := os.Open("test.txt")
if err != nil {
	// handle the error
}
// use

The Go type system doesn't force the resource to be closed and one might forget to close it. This leads to resource leaks which are hard to detect in large code bases. Also, there are many more resources than just files, so one might not know which ones must be closed and which ones do not need to be closed. Oh, you think you will never forget to close your resources? Okay, let's take a quiz: Which ones must be closed and how do they need to be closed?

a, _ := os.Open("test.txt")
b := bytes.NewBuffer([]byte{})
c, _ := http.Get("http://go.dev")
d := time.NewTicker(time.Second)

Answers are:

a) Must be closed with Close.

b) Need not to be closed.

c) Response itself needs not to be closed. However, the Body must be closed with Close.

d) Must be closed with Stop.

See? It is hard. From now on, just wrap every resource that must be closed into a Bracket and do not leak resources anymore. Use the provided wrappers by the package. If the wrapper for your resource is not available already, just wrap it yourself. It needs to be done only once and it is not hard.

Footgun 2: Deferring Close immediately.

f, err := os.Open("test.txt")
defer f.Close()
if err != nil {
	// handle
}
// use

See any problem with the code above? Yeah, if the os.Open returns an error f is going to be nil and you're gonna get a NullPointerException. You cannot escape your Java nightmares even if you run away to Go.

Footgun 3: Ignoring the error returned by deferred Close.

f, err := os.Open("test.txt")
if err != nil {
	// handle
}
defer f.Close()
// use

Resources that implement Closer might return an error. You need to handle it like this:

f, err := os.Open("test.txt")
if err != nil {
	// handle the error
}
defer func() {
	err := f.Close()
	if err != nil {
		// handle the error
	}
}()
// use

I hope these examples are enough.

How?

For manual management: Bracket. For semi-automatic management: WithResource. For automatic management one has to write tailored functions specifically for each resource or use provided functions such as pkg/github.com/thelissimus/brago/os.WithOpen.

Let's fix previous examples with this package.

Every foot gun is solved simply by wrapping the resource in Bracket or if the resource implements io.Closer by wrapping it in WithResource.

Footgun 1:

func WithOpen(name string, use func(*os.File) error) error {
	return WithResource(func() (*os.File, error) { return os.Open(name) }, use)
}

func main() {
	err := WithOpen("test.txt", func(f *os.File) error {
		// use
		return nil
	})
	if err != nil {
		// handle the error
	}
}

You won't forget to close it because it is done automatically by WithResource. Also, there are some wrappers provided by the package for common resources. This foot gun could've been solved by just using pkg/github.com/thelissimus/brago/os.WithOpen.

Footgun 1 quiz:

a) Same solution as the above "Footgun 1" solution.

c) Solution:

func WithHttpResponse(acquire func() (*http.Response, error), use func(r *http.Response) error) error {
	return Bracket(acquire, func(r *http.Response) error { return r.Body.Close() }, use)
}

func main() {
	err := WithHttpResponse(func() (*http.Response, error) { return http.Get("http://go.dev") }, func(r *http.Response) error {
		// use
		return nil
	})
	if err != nil {
		// handle the error
	}
}

d) Solution:

func WithTicker(t time.Duration, use func(r *time.Ticker) error) error {
	return Bracket(
		func() (*time.Ticker, error) { return time.NewTicker(t), nil },
		func(r *time.Ticker) error { r.Stop(); return nil },
		use,
	)
}

func main() {
	err := WithTicker(time.Second, func(r *time.Ticker) error {
		// use
		return nil
	})
	if err != nil {
		// handle the error
	}
}

Footguns 2 and 3 are solved similarly to Footgot 1.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bracket

func Bracket[R any](acquire func() (R, error), release func(R) error, use func(R) error) error

Bracket is used to manually acquire and release the resource.

Example
package main

import (
	"os"

	"github.com/thelissimus/brago"
)

func main() {
	brago.Bracket(
		func() (*os.File, error) {
			return os.OpenFile("./LICENSE", os.O_RDWR|os.O_CREATE, 0644)
		},
		func(r *os.File) error {
			return r.Close()
		},
		func(r *os.File) error {
			_, err := r.WriteString("")
			return err
		},
	)
}

func WithResource

func WithResource[R io.Closer](acquire func() (R, error), use func(R) error) error

WithResource is used to manually acquire and automatically release the resource which implements io.Closer.

Example
package main

import (
	"os"

	"github.com/thelissimus/brago"
)

func main() {
	brago.WithResource(
		func() (*os.File, error) {
			return os.OpenFile("./LICENSE", os.O_RDWR|os.O_CREATE, 0644)
		},
		func(r *os.File) error {
			_, err := r.WriteString("")
			return err
		},
	)
}

Types

This section is empty.

Directories

Path Synopsis
Wrappers of brago for stdlib os package.
Wrappers of brago for stdlib os package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL