Documentation
¶
Overview ¶
Package scope provides iterators that automatically closing, unlocking or canceling resources such as files when they are no longer needed.
The iterator returned by this package will execute the loop body exactly once.
The defer idiom is commonly used in Go to allocate and release resources.
file, err := os.Open(name)
if err != nil {
panic(err)
}
defer file.Close()
_ = file // use file here
Deferred actions are executed when the function returns, so the release of resources is delayed until the function returns.
Sometimes that's what you want, and sometimes it's not.
If you need to release resources immediately, you can use a function literal to specify the scope:
func() {
file, err := os.Open(name)
if err != nil {
return
}
defer file.Close()
_ = file // use file here
}()
This package provides another solution for such situations. The following code snippet achieves the same result as the code above.
for file, err := range scope.Use2(os.Open(name)) {
if err != nil {
break
}
_ = file // use file here
}
Index ¶
- func Context(ctx context.Context, cancel context.CancelFunc) iter.Seq[context.Context]
- func Lock[Mu sync.Locker](mu Mu) iter.Seq[Mu]
- func Tx[Tx Rollbacker](tx Tx) iter.Seq[Tx]
- func Tx2[Tx Rollbacker](tx Tx, err error) iter.Seq2[Tx, error]
- func Use[T io.Closer](resource T) iter.Seq[T]
- func Use2[T io.Closer](resource T, err error) iter.Seq2[T, error]
- func WithCancel(parent context.Context) iter.Seq[context.Context]
- func WithDeadline(parent context.Context, d time.Time) iter.Seq[context.Context]
- func WithTimeout(parent context.Context, timeout time.Duration) iter.Seq[context.Context]
- func WithTimeoutCause(parent context.Context, timeout time.Duration, cause error) iter.Seq[context.Context]
- type Rollbacker
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Context ¶
Context returns an iterator that yields ctx as is. The loop body will be executed exactly once. The cancel will be called after the loop body executed.
Example ¶
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/goaux/scope"
)
func main() {
var g sync.WaitGroup
for ctx := range scope.Context(context.WithTimeout(context.TODO(), 100*time.Millisecond)) {
fmt.Println("pass 0")
g.Add(1)
go func() {
defer g.Done()
<-ctx.Done()
fmt.Println("pass 1", ctx.Err())
}()
// ctx will be canceled at the end of the loop body.
}
g.Wait()
fmt.Println("ok")
}
Output: pass 0 pass 1 context canceled ok
func Lock ¶
Lock returns an iterator that yields a locked mu. The loop body executes exactly once. mu.Lock is called before the loop body executes. mu.Unlock is called after the loop body executes.
Example ¶
package main
import (
"fmt"
"sync"
"github.com/goaux/scope"
)
func main() {
var mu sync.Mutex
for range scope.Lock(&mu) {
// mu is locked.
fmt.Println("pass 0")
}
fmt.Println("pass 1")
}
Output: pass 0 pass 1
Example (Inspect) ¶
package main
import (
"fmt"
"sync"
"github.com/goaux/scope"
)
func main() {
var mu TestLocker
for range scope.Lock(&mu) {
// mu is locked for writing.
fmt.Println("pass 0")
// mu will be unlocked at the end of the loop body.
}
fmt.Println("pass 1")
for range scope.Lock(mu.RLocker()) {
// mu is locked for reading.
fmt.Println("pass 2")
// mu will be unlocked at the end of the loop body.
}
fmt.Println("pass 3")
}
type TestLocker struct{}
func (*TestLocker) Lock() { fmt.Println("Lock") }
func (*TestLocker) Unlock() { fmt.Println("Unlock") }
func (*TestLocker) RLock() { fmt.Println("RLock") }
func (*TestLocker) RUnlock() { fmt.Println("RUnlock") }
func (*TestLocker) RLocker() sync.Locker { return (*rlocker)(nil) }
type rlocker TestLocker
func (r *rlocker) Lock() { (*TestLocker)(r).RLock() }
func (r *rlocker) Unlock() { (*TestLocker)(r).RUnlock() }
Output: Lock pass 0 Unlock pass 1 RLock pass 2 RUnlock pass 3
Example (RWMutex) ¶
package main
import (
"fmt"
"sync"
"github.com/goaux/scope"
)
func main() {
var mu sync.RWMutex
for range scope.Lock(&mu) {
// mu is locked for writing.
fmt.Println("pass 0")
// mu will be unlocked at the end of the loop body.
}
fmt.Println("pass 1")
for range scope.Lock(mu.RLocker()) {
// mu is locked for reading.
fmt.Println("pass 2")
}
fmt.Println("pass 3")
}
Output: pass 0 pass 1 pass 2 pass 3
func Tx ¶
func Tx[Tx Rollbacker](tx Tx) iter.Seq[Tx]
Context returns an iterator that yields tx as is. The loop body will be executed exactly once. tx.Rollback will be called after the loop body executed.
Example ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/goaux/scope"
)
func main() {
for db, err := range scope.Use2(sql.Open("sql_test", "ExampleOpenDB")) {
if err != nil {
fmt.Println(err)
break
}
tx, err := db.Begin()
if err != nil {
fmt.Println(err)
break
}
for tx := range scope.Tx(tx) {
// do something.
_ = tx
// Not committing implies a rollback
}
fmt.Println("end#1")
}
fmt.Println("end#2")
}
func init() {
sql.Register("sql_test", Driver{})
}
type Driver struct{}
func (Driver) Open(name string) (driver.Conn, error) {
fmt.Printf("Driver.Open(%q)\n", name)
return Conn{}, nil
}
func (Driver) OpenConnector(name string) (driver.Connector, error) {
fmt.Printf("Driver.OpenConnector(%q)\n", name)
return Connector{}, nil
}
type Connector struct{}
func (Connector) Connect(context.Context) (driver.Conn, error) {
fmt.Println("Connector.Connect(ctx)")
return Conn{}, nil
}
func (Connector) Driver() driver.Driver {
return Driver{}
}
type Conn struct{}
func (Conn) Prepare(query string) (driver.Stmt, error) {
fmt.Printf("Conn.Prepare(%q)\n", query)
return Stmt{}, nil
}
func (Conn) Close() error {
fmt.Println("Conn.Close()")
return nil
}
func (Conn) Begin() (driver.Tx, error) {
fmt.Println("Conn.Begin()")
return Tx{}, nil
}
func (Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
p, _ := json.Marshal(opts)
fmt.Printf("Conn.BeginTx(ctx, %s)\n", string(p))
return Tx{}, nil
}
type Stmt struct{}
func (Stmt) Close() error {
fmt.Println("Stmt.Close()")
return nil
}
func (Stmt) NumInput() int {
fmt.Println("Stmt.NumInput()")
return 0
}
func (Stmt) Exec(args []driver.Value) (driver.Result, error) {
p, _ := json.Marshal(args)
fmt.Printf("Stmt.Exec(%s)", string(p))
return Result{}, nil
}
func (Stmt) Query(args []driver.Value) (driver.Rows, error) {
p, _ := json.Marshal(args)
fmt.Printf("Stmt.Query(%s)", string(p))
return Rows{}, nil
}
type Tx struct{}
func (Tx) Commit() error {
fmt.Println("Tx.Commit()")
return nil
}
func (Tx) Rollback() error {
fmt.Println("Tx.Rollback()")
return nil
}
type Result struct{}
func (Result) LastInsertId() (int64, error) {
fmt.Println("Result.LastInsertId()")
return 0, nil
}
func (Result) RowsAffected() (int64, error) {
fmt.Println("Result.RowsAffected()")
return 0, nil
}
type Rows struct{}
func (Rows) Columns() []string {
fmt.Println("Rows.Columns()")
return nil
}
// Close closes the rows iterator.
func (Rows) Close() error {
fmt.Println("Rows.Close()")
return nil
}
func (Rows) Next(dest []driver.Value) error {
p, _ := json.Marshal(dest)
fmt.Printf("Rows.Next(%s)\n", string(p))
return nil
}
Output: Driver.OpenConnector("ExampleOpenDB") Connector.Connect(ctx) Conn.BeginTx(ctx, {"Isolation":0,"ReadOnly":false}) Tx.Rollback() end#1 Conn.Close() end#2
func Tx2 ¶
func Tx2[Tx Rollbacker](tx Tx, err error) iter.Seq2[Tx, error]
Context returns an iterator that yields tx and err as is. The loop body will be executed exactly once. If err is nil, tx.Rollback will be called after the loop body executed.
Example (Begin) ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/goaux/scope"
)
func main() {
for db, err := range scope.Use2(sql.Open("sql_test", "ExampleOpenDB")) {
if err != nil {
fmt.Println(err)
break
}
for tx, err := range scope.Tx2(db.Begin()) {
if err != nil {
fmt.Println(err)
break
}
// do something.
_ = tx
// Under normal circumstances, Commit is called last.
if err := tx.Commit(); err != nil {
fmt.Println(err)
break
}
fmt.Println("end#1")
}
for tx, err := range scope.Tx2(db.Begin()) {
if err != nil {
fmt.Println(err)
break
}
// do something.
_ = tx
// Not committing implies a rollback
fmt.Println("end#2")
}
fmt.Println("end#3")
}
fmt.Println("end#4")
}
func init() {
sql.Register("sql_test", Driver{})
}
type Driver struct{}
func (Driver) Open(name string) (driver.Conn, error) {
fmt.Printf("Driver.Open(%q)\n", name)
return Conn{}, nil
}
func (Driver) OpenConnector(name string) (driver.Connector, error) {
fmt.Printf("Driver.OpenConnector(%q)\n", name)
return Connector{}, nil
}
type Connector struct{}
func (Connector) Connect(context.Context) (driver.Conn, error) {
fmt.Println("Connector.Connect(ctx)")
return Conn{}, nil
}
func (Connector) Driver() driver.Driver {
return Driver{}
}
type Conn struct{}
func (Conn) Prepare(query string) (driver.Stmt, error) {
fmt.Printf("Conn.Prepare(%q)\n", query)
return Stmt{}, nil
}
func (Conn) Close() error {
fmt.Println("Conn.Close()")
return nil
}
func (Conn) Begin() (driver.Tx, error) {
fmt.Println("Conn.Begin()")
return Tx{}, nil
}
func (Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
p, _ := json.Marshal(opts)
fmt.Printf("Conn.BeginTx(ctx, %s)\n", string(p))
return Tx{}, nil
}
type Stmt struct{}
func (Stmt) Close() error {
fmt.Println("Stmt.Close()")
return nil
}
func (Stmt) NumInput() int {
fmt.Println("Stmt.NumInput()")
return 0
}
func (Stmt) Exec(args []driver.Value) (driver.Result, error) {
p, _ := json.Marshal(args)
fmt.Printf("Stmt.Exec(%s)", string(p))
return Result{}, nil
}
func (Stmt) Query(args []driver.Value) (driver.Rows, error) {
p, _ := json.Marshal(args)
fmt.Printf("Stmt.Query(%s)", string(p))
return Rows{}, nil
}
type Tx struct{}
func (Tx) Commit() error {
fmt.Println("Tx.Commit()")
return nil
}
func (Tx) Rollback() error {
fmt.Println("Tx.Rollback()")
return nil
}
type Result struct{}
func (Result) LastInsertId() (int64, error) {
fmt.Println("Result.LastInsertId()")
return 0, nil
}
func (Result) RowsAffected() (int64, error) {
fmt.Println("Result.RowsAffected()")
return 0, nil
}
type Rows struct{}
func (Rows) Columns() []string {
fmt.Println("Rows.Columns()")
return nil
}
// Close closes the rows iterator.
func (Rows) Close() error {
fmt.Println("Rows.Close()")
return nil
}
func (Rows) Next(dest []driver.Value) error {
p, _ := json.Marshal(dest)
fmt.Printf("Rows.Next(%s)\n", string(p))
return nil
}
Output: Driver.OpenConnector("ExampleOpenDB") Connector.Connect(ctx) Conn.BeginTx(ctx, {"Isolation":0,"ReadOnly":false}) Tx.Commit() end#1 Conn.BeginTx(ctx, {"Isolation":0,"ReadOnly":false}) end#2 Tx.Rollback() end#3 Conn.Close() end#4
Example (BeginTx) ¶
package main
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/goaux/scope"
)
func main() {
for db, err := range scope.Use2(sql.Open("sql_test", "ExampleOpenDB")) {
if err != nil {
fmt.Println(err)
break
}
ctx := context.TODO()
for tx, err := range scope.Tx2(db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})) {
if err != nil {
fmt.Println(err)
break
}
// do something.
_ = tx
if err := tx.Commit(); err != nil {
fmt.Println(err)
break
}
fmt.Println("end#1")
}
for tx, err := range scope.Tx2(db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})) {
if err != nil {
fmt.Println(err)
break
}
// do something.
_ = tx
// Not committing implies a rollback
fmt.Println("end#2")
}
fmt.Println("end#3")
}
fmt.Println("end#4")
}
func init() {
sql.Register("sql_test", Driver{})
}
type Driver struct{}
func (Driver) Open(name string) (driver.Conn, error) {
fmt.Printf("Driver.Open(%q)\n", name)
return Conn{}, nil
}
func (Driver) OpenConnector(name string) (driver.Connector, error) {
fmt.Printf("Driver.OpenConnector(%q)\n", name)
return Connector{}, nil
}
type Connector struct{}
func (Connector) Connect(context.Context) (driver.Conn, error) {
fmt.Println("Connector.Connect(ctx)")
return Conn{}, nil
}
func (Connector) Driver() driver.Driver {
return Driver{}
}
type Conn struct{}
func (Conn) Prepare(query string) (driver.Stmt, error) {
fmt.Printf("Conn.Prepare(%q)\n", query)
return Stmt{}, nil
}
func (Conn) Close() error {
fmt.Println("Conn.Close()")
return nil
}
func (Conn) Begin() (driver.Tx, error) {
fmt.Println("Conn.Begin()")
return Tx{}, nil
}
func (Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
p, _ := json.Marshal(opts)
fmt.Printf("Conn.BeginTx(ctx, %s)\n", string(p))
return Tx{}, nil
}
type Stmt struct{}
func (Stmt) Close() error {
fmt.Println("Stmt.Close()")
return nil
}
func (Stmt) NumInput() int {
fmt.Println("Stmt.NumInput()")
return 0
}
func (Stmt) Exec(args []driver.Value) (driver.Result, error) {
p, _ := json.Marshal(args)
fmt.Printf("Stmt.Exec(%s)", string(p))
return Result{}, nil
}
func (Stmt) Query(args []driver.Value) (driver.Rows, error) {
p, _ := json.Marshal(args)
fmt.Printf("Stmt.Query(%s)", string(p))
return Rows{}, nil
}
type Tx struct{}
func (Tx) Commit() error {
fmt.Println("Tx.Commit()")
return nil
}
func (Tx) Rollback() error {
fmt.Println("Tx.Rollback()")
return nil
}
type Result struct{}
func (Result) LastInsertId() (int64, error) {
fmt.Println("Result.LastInsertId()")
return 0, nil
}
func (Result) RowsAffected() (int64, error) {
fmt.Println("Result.RowsAffected()")
return 0, nil
}
type Rows struct{}
func (Rows) Columns() []string {
fmt.Println("Rows.Columns()")
return nil
}
// Close closes the rows iterator.
func (Rows) Close() error {
fmt.Println("Rows.Close()")
return nil
}
func (Rows) Next(dest []driver.Value) error {
p, _ := json.Marshal(dest)
fmt.Printf("Rows.Next(%s)\n", string(p))
return nil
}
Output: Driver.OpenConnector("ExampleOpenDB") Connector.Connect(ctx) Conn.BeginTx(ctx, {"Isolation":6,"ReadOnly":false}) Tx.Commit() end#1 Conn.BeginTx(ctx, {"Isolation":6,"ReadOnly":false}) end#2 Tx.Rollback() end#3 Conn.Close() end#4
func Use ¶
Use returns an iterator that yields resource as is. The loop body will be executed exactly once. resource.Close will be called after the loop body executed.
Example ¶
package main
import (
"bytes"
"compress/gzip"
"encoding/hex"
"fmt"
"github.com/goaux/scope"
)
func main() {
buf := &bytes.Buffer{}
for gz := range scope.Use(gzip.NewWriter(buf)) {
fmt.Fprintln(gz, "hello world")
}
fmt.Println(hex.EncodeToString(buf.Bytes()))
}
Output: 1f8b08000000000000ffca48cdc9c95728cf2fca49e102040000ffff2d3b08af0c000000
func Use2 ¶
Use2 returns an iterator that yields resource and err as is. The loop body will be executed exactly once. If err is nil, resource.Close will be called after the loop body executed.
Example ¶
package main
import (
"compress/gzip"
"fmt"
"io"
"os"
"github.com/goaux/scope"
)
func main() {
defer os.Remove("test.gz")
for file, err := range scope.Use2(os.Create("test.gz")) {
if err != nil {
fmt.Println(err)
break
}
for gz := range scope.Use(gzip.NewWriter(file)) {
if _, err := fmt.Fprintln(gz, "hello world"); err != nil {
fmt.Println(err)
break
}
// gz will be closed at the end of the loop body.
}
// file will be closed at the end of the loop body.
}
for file, err := range scope.Use2(os.Open("test.gz")) {
if err != nil {
fmt.Println(err)
break
}
if gz, err := gzip.NewReader(file); err != nil {
fmt.Println(err)
break
} else if b, err := io.ReadAll(gz); err != nil {
fmt.Println(err)
} else {
fmt.Print(string(b))
}
// file will be closed at the end of the loop body.
}
}
Output: hello world
func WithCancel ¶
WithCancel returns Context(context.WithCancel(parent)).
See Context, context.WithCancel.
Example ¶
package main
import (
"context"
"fmt"
"sync"
"github.com/goaux/scope"
)
func main() {
var g sync.WaitGroup
for ctx := range scope.WithCancel(context.TODO()) {
fmt.Println("pass 0")
g.Add(1)
go func() {
defer g.Done()
<-ctx.Done()
fmt.Println("pass 1")
}()
// ctx will be canceled at the end of the loop body.
}
g.Wait()
fmt.Println("ok")
}
Output: pass 0 pass 1 ok
func WithDeadline ¶
WithDeadline returns Context(context.WithDeadline(parent, d)).
See Context, context.WithDeadline.
Example ¶
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/goaux/scope"
)
func main() {
var g sync.WaitGroup
for ctx := range scope.WithDeadline(context.TODO(), time.Now().Add(100*time.Millisecond)) {
fmt.Println("pass 0")
g.Add(1)
go func() {
defer g.Done()
<-ctx.Done()
fmt.Println("pass 1", ctx.Err())
}()
// ctx will be canceled at the end of the loop body.
}
g.Wait()
fmt.Println("ok")
}
Output: pass 0 pass 1 context canceled ok
func WithTimeout ¶
WithTimeout returns Context(context.WithTimeout(parent, timeout)).
See Context, context.WithTimeout.
Example ¶
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/goaux/scope"
)
func main() {
var g sync.WaitGroup
for ctx := range scope.WithTimeout(context.TODO(), 100*time.Millisecond) {
fmt.Println("pass 0")
g.Add(1)
go func() {
defer g.Done()
<-ctx.Done()
fmt.Println("pass 1", ctx.Err())
}()
// ctx will be canceled at the end of the loop body.
}
g.Wait()
fmt.Println("ok")
}
Output: pass 0 pass 1 context canceled ok
func WithTimeoutCause ¶
func WithTimeoutCause( parent context.Context, timeout time.Duration, cause error, ) iter.Seq[context.Context]
WithTimeoutCause returns Context(context.WithTimeoutCause(parent, timeout, cause)).
See Context, context.WithTimeoutCause.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/goaux/scope"
)
func main() {
var g sync.WaitGroup
for ctx := range scope.WithTimeoutCause(context.TODO(), 100*time.Millisecond, errors.New("hello")) {
fmt.Println("pass 0")
g.Add(1)
go func() {
defer g.Done()
<-ctx.Done()
fmt.Println("pass 1", ctx.Err())
}()
// ctx will be canceled at the end of the loop body.
}
g.Wait()
fmt.Println("ok")
}
Output: pass 0 pass 1 context canceled ok
Types ¶
type Rollbacker ¶
type Rollbacker interface {
Rollback() error
}