lcl

package module
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 9 Imported by: 0

README

lcl - the "water of life" for mental synchronization

Release Test Go Reference Go Report Card codecov

A generic Go utility library providing small, handy building blocks for application code.

Requires Go 1.26+

go get github.com/abagile/tokyo3-lcl

Design philosophy

Simple slice-in/slice-out helpers

The collection helpers in slices.go operate directly on concrete slices using plain Go loops. This keeps the package dependency-light and makes allocation and control flow easy to read.

Filtering, grouping, partitioning, and padding functions preserve named slice types through S ~[]T. Mapping functions return []R because the element type can change.

These helpers are intentionally eager: each function consumes its input and returns a concrete slice or value. Chaining helpers is therefore easy to read, but it allocates intermediate slices:

activeNames := Map(
    Filter(users, func(u User) bool { return u.Active }),
    func(u User) string { return u.Name },
)

If you need lazy iterator pipelines, non-slice sources, or allocation-free composition across multiple transformations, use Go's iter package directly or a library such as github.com/BooleanCat/go-functional.

Small panic-on-failure helpers, not a Result monad

result.go keeps normal Go (T, error) values visible and provides small Must* helpers for CLI tools and startup paths where a failure should abort immediately. For normal request/runtime code, prefer explicit error handling.


slices.go — Functional slice operations

Function naming conventions

Each operation comes in up to three variants:

Suffix Meaning
(none) Plain value callback
2 Index-aware callback receiving (index, value)
Error Callback may return an error; iteration stops on the first non-nil error

Reach for the plain variant by default; add 2 when you need the position, and add Error when the callback can fail.

Filtering
Function Description
Filter(xs, pred) Keep elements where pred is true
Filter2(xs, pred) Like Filter but predicate receives (index, value)
FilterError(xs, pred) Like Filter but predicate may return an error
Exclude(xs, pred) Keep elements where pred is false
Exclude2(xs, pred) Like Exclude with index-aware predicate
ExcludeError(xs, pred) Like Exclude but predicate may return an error
evens := Filter([]int{1, 2, 3, 4}, func(n int) bool {
    return n%2 == 0
})
// []int{2, 4}

oddPositions := Exclude2([]string{"a", "b", "c", "d"}, func(i int, _ string) bool {
    return i%2 == 0
})
// []string{"b", "d"}
Transformation
Function Description
Map(xs, mapper) Transform each element
Map2(xs, mapper) Transform with (index, value) mapper
MapError(xs, mapper) Transform; stops on first error
labels := Map([]int{10, 20, 30}, func(n int) string {
    return fmt.Sprintf("value=%d", n)
})

indexed := Map2([]string{"a", "b"}, func(i int, s string) string {
    return fmt.Sprintf("%d:%s", i, s)
})
Folding
Function Description
Fold(xs, accum, initial) Left fold
Fold2(xs, accum, initial) Left fold with index
FoldError(xs, accum, initial) Left fold; stops on first error
FoldRight(xs, accum, initial) Right fold
FoldRight2(xs, accum, initial) Right fold with index
FoldRightError(xs, accum, initial) Right fold; stops on first error
sum := Fold([]int{1, 2, 3}, func(acc, n int) int {
    return acc + n
}, 0)
// 6

reversed := FoldRight([]string{"a", "b", "c"}, func(acc, s string) string {
    return acc + s
}, "")
// "cba"
Iteration
Function Description
ForEach(xs, f) Call f for every element
ForEach2(xs, f) Call f(index, value) for every element
ForEachWhile(xs, pred) Iterate while pred returns true
ForEach(files, func(path string) {
    log.Println("processing", path)
})

ForEachWhile(events, func(e Event) bool {
    return handle(e) // return false to stop early
})
Grouping
Function Description
GroupBy(xs, mapper) map[R]S — groups share the same key; order within each group is preserved
PartitionBy(xs, mapper) []S — like GroupBy, but groups are returned in first-seen key order
FrequenciesBy(xs, mapper) map[R]int — count occurrences by key

Pass Id as the mapper when the element itself is the key:

// group/count by the value itself
GroupBy(words, Id)        // map[string][]string
FrequenciesBy(words, Id)  // map[string]int — equivalent to a word-count
PartitionBy(runs, Id)     // ordered groups, e.g. [1,1,2,2,1] → [[1,1,1],[2,2]]
Padding
Function Description
Pad(xs, n) Extend slice to length n with zero values; panics if n < 0
PadWith(xs, n, padding) Like Pad but fills with padding
Pad([]int{1, 2}, 5)          // []int{1, 2, 0, 0, 0}
PadWith([]int{1, 2}, 5, 99)  // []int{1, 2, 99, 99, 99}

result.go — Panic-on-failure helpers

Designed for CLI tools and startup paths where certain failures have no sensible recovery action — a required environment variable is missing, a database connection must succeed, or a hard dependency cannot be reached. The helpers panic with a clear message on failure, or return the value and move on.

func MustGet[T any](value T, err error) T
func MustPass(err error, msg ...any)
func MustPresent[T comparable](value T, msg ...any) T
  • MustGet — unwraps a (T, error) pair. Panics with the original error when err != nil.
  • MustPass — for error-only calls. Optional message arguments add context; when present, msg[0] is the fmt.Sprintf format string and msg[1:] are its arguments.
  • MustPresent — panics when the value is the zero value; otherwise returns it. The value must be comparable so the check can use direct ==. Optional message arguments follow the same fmt.Sprintf convention as MustPass.

All Must* helpers panic with an error, so recovery code can consistently inspect recover() values as errors.

Examples:

db := MustGet(sql.Open("pgx", dsn))
MustPass(db.PingContext(ctx))
MustPass(db.PingContext(ctx), "database ping failed")
format := "database ping failed for %s"
MustPass(db.PingContext(ctx), format, dsn)

redisHost := MustPresent(os.Getenv("REDIS_HOST"), "REDIS_HOST must not be empty")

For request handlers, library APIs, and recoverable failures, prefer normal Go error returns instead of panics.


math.go — Statistical helpers

MinMax
func MinMax[T cmp.Ordered](a, b T) (min, max T)

Returns (min, max) regardless of argument order.

Clamp
func Clamp[T cmp.Ordered](value, min, max T) T

Clamps value into [min, max]. Caller must ensure min <= max.

Mean
func Mean[T Number](xs []T) (T, bool)

Returns the arithmetic mean of the slice. Returns (zero, false) for an empty slice.

Mode
func Mode[T comparable](xs []T) ([]T, bool)

Returns all values that appear with the highest frequency. Returns ([], false) for an empty slice. Order of results is non-deterministic when multiple values tie.


types.go — Generic type utilities

Zero / emptiness
func Empty[T any]() T                          // returns zero value of T
func IsEmpty[T comparable](v T) bool           // v == zero
func IsNotEmpty[T comparable](v T) bool        // v != zero
Coalesce
func Coalesce[T comparable](values ...T) (T, bool)   // first non-zero value
func Coalesced[T comparable](values ...T) T           // first non-zero, or zero
Pointer helpers
func ToPtr[T comparable](v T) *T             // nil if v is zero
func FromPtr[T any](ptr *T, fallback ...T) T // dereference or fallback/zero
Slice conversion
func ToAnySlice[T any](in []T) []any
func FromAnySlice[T any](in []any) ([]T, bool)   // false if any element cannot be asserted to T
Nested data access

GetIn and SetIn navigate nested data structures using a dot-separated path string. Slice elements are accessed by numeric index.

func GetIn(data any, path string) (any, error)
func SetIn(data any, path string, value any) error

GetIn supports map[string]any, map[any]any, and []any while traversing. SetIn supports map[string]any and []any; it automatically creates intermediate map[string]any nodes for missing map keys. It does not grow slices — the index must already be in bounds.

data := map[string]any{
    "user": map[string]any{
        "scores": []any{10, 20, 30},
    },
}

v, _ := GetIn(data, "user.scores.1")  // 20
SetIn(data, "user.scores.1", 99)      // scores[1] = 99
Identity
func Id[T any](v T) T

Returns its argument unchanged. Primarily useful as a first-class mapper when the element itself should be the key — avoiding the need to write func(x T) T { return x } at every call site:

FrequenciesBy(tags, Id)    // count each tag
GroupBy(events, Id)        // bucket identical events together
PartitionBy(tokens, Id)    // ordered groups of equal tokens

tuples.go — Tuple types and zip/unzip

Types
type Tuple2[A, B any]       struct{ A A; B B }
type Tuple3[A, B, C any]    struct{ A A; B B; C C }
type Tuple4[A, B, C, D any] struct{ A A; B B; C C; D D }

Each type has an Unpack() method returning the fields as multiple return values.

Constructors
T2(a, b)          Tuple2[A, B]
T3(a, b, c)       Tuple3[A, B, C]
T4(a, b, c, d)    Tuple4[A, B, C, D]
Zip

Combines multiple slices into a slice of tuples. Output length equals the shortest input.

Zip2(a []A, b []B)             []Tuple2[A, B]
Zip3(a []A, b []B, c []C)      []Tuple3[A, B, C]
Zip4(a, b, c, d)               []Tuple4[A, B, C, D]
Unzip

Splits a slice of tuples back into individual slices.

Unzip2(tuples []Tuple2[A, B])          ([]A, []B)
Unzip3(tuples []Tuple3[A, B, C])       ([]A, []B, []C)
Unzip4(tuples []Tuple4[A, B, C, D])    ([]A, []B, []C, []D)
CrossJoin

Produces the Cartesian product of the input slices. Returns an empty slice immediately if any input is empty.

CrossJoin2(a, b)       []Tuple2[A, B]
CrossJoin3(a, b, c)    []Tuple3[A, B, C]
CrossJoin4(a, b, c, d) []Tuple4[A, B, C, D]
Usage patterns

Tuples are best suited for local, short-lived grouping within a single function or pipeline — situations where defining a one-off named struct would be more ceremony than it is worth. If the same pairing appears in more than one place, or is part of a public API, a named struct is almost always clearer.

1. Pairing a sort key with its original value

// attach a score without losing the original user
ranked := Map(users, func(u User) Tuple2[int, User] {
    return T2(scoreOf(u), u)
})

2. Zipping parallel slices from separate data sources

// two separate API responses that logically belong together
rows := Zip2(invoiceIDs, amounts)
ForEach(rows, func(row Tuple2[string, float64]) {
    id, amount := row.Unpack()
    process(id, amount)
})

3. CrossJoin for generating combinations (e.g. test matrix)

envs    := []string{"staging", "prod"}
regions := []string{"us-east", "eu-west", "ap-south"}

ForEach(CrossJoin2(envs, regions), func(tc Tuple2[string, string]) {
    env, region := tc.Unpack()
    deploy(env, region)
})

4. Unzip to split a parsed CSV into typed columns

// "alice,30"  "bob,25"  "carol,28"
names, ages := Unzip2(Map(rows, func(row string) Tuple2[string, int] {
    parts := strings.SplitN(row, ",", 2)
    age, _ := strconv.Atoi(parts[1])
    return T2(parts[0], age)
}))

5. Carrying the original alongside a transformed value in a pipeline

// keep original for later comparison after normalisation
pairs := Map(inputs, func(s string) Tuple2[string, string] {
    return T2(s, normalize(s))
})
changed := Filter(pairs, func(p Tuple2[string, string]) bool {
    return p.A != p.B
})

eventbus.go — In-process typed event bus

A lightweight, thread-safe event bus for decoupling components within a single process. Handlers are registered per event type using Go generics — the type key is derived via reflection at subscription time, so callers do not need to supply manual type tokens.

Publishing fans out to all matching handlers concurrently in separate goroutines and blocks until every handler returns, making Publish synchronous from the caller's perspective.

A sync.RWMutex protects the handler map. Subscribe takes a write lock while Publish snapshots the relevant handler slice under a read lock and then releases it before dispatching, so subscriptions from other goroutines are not blocked by long-running handlers.

Every handler receives the caller's context.Context. If the context is already cancelled when Publish is called, no handlers run. Handlers are responsible for checking ctx.Err() themselves if they perform long work — the bus does not cancel in-flight handlers mid-execution.

Normal usage
type OrderPlaced struct { OrderID string }
type PaymentFailed struct { Reason string }

bus := NewEventBus()

Subscribe(bus, func(ctx context.Context, e OrderPlaced) {
    fmt.Println("order placed:", e.OrderID)
})

Subscribe(bus, func(ctx context.Context, e PaymentFailed) {
    fmt.Println("payment failed:", e.Reason)
})

Publish(context.Background(), bus, OrderPlaced{OrderID: "ord-123"})
Publish(context.Background(), bus, PaymentFailed{Reason: "insufficient funds"})
Audit/logging handler with any

Subscriptions are keyed by the static generic event type. A subscription to any is useful for audit/logging paths, but it only receives events that are published as Publish[any](...).

Subscribe(bus, func(ctx context.Context, e any) {
    switch ev := e.(type) {
    case OrderPlaced:
        log.Printf("audit: order %s placed", ev.OrderID)
    case PaymentFailed:
        log.Printf("audit: payment failed — %s", ev.Reason)
    default:
        log.Printf("audit: event %T", ev)
    }
})

// Must be published as any to match the any subscription key.
Publish[any](context.Background(), bus, OrderPlaced{OrderID: "ord-123"})

pipeline.go — Context-aware channel pipelines

PipeContext sends initial values into a pipeline input channel. Pipe transforms values from one channel into another. Each transform receives context.Context, and the pipeline stops when the input channel closes or the context is canceled. Callers should either drain the output channel or cancel the context to let the pipeline goroutine exit.

numbers := PipeContext(ctx, 1, 2, 3)

doubled := Pipe(ctx, numbers, func(ctx context.Context, n int) int {
    return n * 2
})
labels := Pipe(ctx, doubled, func(ctx context.Context, n int) string {
    return fmt.Sprintf("n=%d", n)
})

// Drain the final channel to execute the full pipeline.
for label := range labels {
    fmt.Println(label)
}

Use PipeError when a transform can fail. It stops at the first error and sends that error on a buffered, single-error channel.

out, errs := PipeError(ctx, jobs, func(ctx context.Context, job Job) (Result, error) {
    return processJob(ctx, job)
})

workerpool.go — Concurrent job processing

Helpers for running work concurrently without managing goroutines directly. Worker counts less than one are normalized to one.

WorkerPool

A reusable pool that reads jobs from a channel and dispatches them to a fixed number of goroutines. Run blocks until the jobs channel is closed and all workers have finished.

type HandlerFunc[T any] func(ctx context.Context, job T, logger *slog.Logger)

func NewWorkerPool[T any](numWorkers int, jobs <-chan T, handler HandlerFunc[T], logger *slog.Logger) *WorkerPool[T]
func (p *WorkerPool[T]) Run(ctx context.Context)

Use WorkerPool when you need control over the jobs channel lifecycle — for example, when jobs arrive from a long-running stream or message queue:

jobCh := make(chan Order)

go func() {
    for order := range messageQueue.Subscribe() {
        jobCh <- order
    }
    close(jobCh)
}()

handler := func(ctx context.Context, order Order, logger *slog.Logger) {
    if err := processOrder(ctx, order); err != nil {
        logger.Error("order failed", "id", order.ID, "err", err)
    }
}

wp := NewWorkerPool(8, jobCh, handler, slog.Default())
wp.Run(ctx) // blocks until jobCh is closed
RunConcurrent

Runs fn concurrently over every element of a slice. Blocks until all jobs are done. Use this when you need side effects but not return values.

func RunConcurrent[T any](ctx context.Context, jobs []T, workerCount int, fn func(context.Context, T))
files := []string{"a.csv", "b.csv", "c.csv"}

RunConcurrent(ctx, files, 4, func(ctx context.Context, path string) {
    if err := uploadFile(ctx, path); err != nil {
        log.Printf("upload failed: %s: %v", path, err)
    }
})
MapConcurrent

Like RunConcurrent but collects results. Output order matches input order regardless of which worker processes which job.

func MapConcurrent[T any, R any](ctx context.Context, jobs []T, workerCount int, fn func(context.Context, T) R) []R
userIDs := []int{1, 2, 3, 4, 5}

profiles := MapConcurrent(ctx, userIDs, 4, func(ctx context.Context, id int) Profile {
    p, err := fetchProfile(ctx, id)
    if err != nil {
        return Profile{} // zero value on failure
    }
    return p
})
// profiles[0] corresponds to userIDs[0], profiles[1] to userIDs[1], etc.

This package intentionally keeps slices.go eager and dependency-free. For lazy iterator evaluation and composable pipelines, consider using Go's standard iter package directly or github.com/BooleanCat/go-functional.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Clamp

func Clamp[T cmp.Ordered](value, mIn, mAx T) T

func Coalesce

func Coalesce[T comparable](values ...T) (T, bool)

func Coalesced

func Coalesced[T comparable](values ...T) T

func Empty

func Empty[T any]() T

func Exclude

func Exclude[T any, S ~[]T](xs S, pred func(T) bool) S

func Exclude2

func Exclude2[T any, S ~[]T](xs S, pred func(int, T) bool) S

func ExcludeError

func ExcludeError[T any, S ~[]T](xs S, pred func(T) (bool, error)) (S, error)

func Filter

func Filter[T any, S ~[]T](xs S, pred func(T) bool) S

func Filter2

func Filter2[T any, S ~[]T](xs S, pred func(int, T) bool) S

func FilterError

func FilterError[T any, S ~[]T](xs S, pred func(T) (bool, error)) (S, error)

func Fold

func Fold[T, R any, S ~[]T](xs S, accum func(R, T) R, initial R) R

func Fold2

func Fold2[T, R any, S ~[]T](xs S, accum func(R, int, T) R, initial R) R

func FoldError

func FoldError[T, R any, S ~[]T](xs S, accum func(R, T) (R, error), initial R) (R, error)

func FoldRight

func FoldRight[T, R any, S ~[]T](xs S, accum func(R, T) R, initial R) R

func FoldRight2

func FoldRight2[T, R any, S ~[]T](xs S, accum func(R, int, T) R, initial R) R

func FoldRightError

func FoldRightError[T, R any, S ~[]T](xs S, accum func(R, T) (R, error), initial R) (R, error)

func ForEach

func ForEach[T any, S ~[]T](xs S, f func(T))

func ForEach2

func ForEach2[T any, S ~[]T](xs S, f func(int, T))

func ForEachWhile

func ForEachWhile[T any, S ~[]T](xs S, pred func(T) bool)

func FrequenciesBy

func FrequenciesBy[T any, R comparable, S ~[]T](xs S, mapper func(T) R) map[R]int

func FromAnySlice

func FromAnySlice[T any](in []any) ([]T, bool)

func FromPtr

func FromPtr[T any](ptr *T, fallback ...T) T

func GetIn

func GetIn(data any, path string) (any, error)

func GroupBy

func GroupBy[T any, R comparable, S ~[]T](xs S, mapper func(T) R) map[R]S

func Id

func Id[T any](v T) T

func IsEmpty

func IsEmpty[T comparable](v T) bool

func IsNotEmpty

func IsNotEmpty[T comparable](v T) bool

func Map

func Map[T, R any, S ~[]T](xs S, mapper func(T) R) []R

func Map2

func Map2[T, R any, S ~[]T](xs S, mapper func(int, T) R) []R

func MapConcurrent

func MapConcurrent[T any, R any](ctx context.Context, jobs []T, workerCount int, fn func(context.Context, T) R) []R

func MapError

func MapError[T, R any, S ~[]T](xs S, mapper func(T) (R, error)) ([]R, error)

func Mean

func Mean[T Number](xs []T) (T, bool)

func MinMax

func MinMax[T cmp.Ordered](mIn, mAx T) (T, T)

func Mode

func Mode[T comparable](xs []T) ([]T, bool)

func MustGet added in v0.4.1

func MustGet[T any](value T, err error) T

func MustPass added in v0.4.0

func MustPass(err error, msg ...any)

func MustPresent added in v0.4.0

func MustPresent[T comparable](value T, msg ...any) T

func Pad

func Pad[T any, S ~[]T](xs S, n int) S

func PadWith

func PadWith[T any, S ~[]T](xs S, n int, padding T) S

func PartitionBy

func PartitionBy[T any, R comparable, S ~[]T](xs S, mapper func(T) R) []S

func Pipe added in v0.4.3

func Pipe[I, O any](ctx context.Context, in <-chan I, transform func(context.Context, I) O) <-chan O

Pipe transforms values from in and returns a channel of transformed values.

The output channel is closed when the input channel is closed or ctx is done. Callers should either drain the output channel or cancel ctx to let the pipeline goroutine exit.

func PipeContext added in v0.4.3

func PipeContext[T any](ctx context.Context, values ...T) <-chan T

PipeContext sends values into a new pipeline input channel.

The output channel is closed after all values are sent or ctx is done.

func PipeError added in v0.4.3

func PipeError[I, O any](ctx context.Context, in <-chan I, transform func(context.Context, I) (O, error)) (<-chan O, <-chan error)

PipeError is like Pipe, but stops at the first transform error.

The error channel is buffered for one error and is closed when the pipeline exits. It receives transform errors only; cancellation is exposed through ctx.

func Publish

func Publish[T any](ctx context.Context, bus *EventBus, event T)

Publish sends an event to all registered handlers for that event type. It passes a context that can be used for cancellation.

func RunConcurrent

func RunConcurrent[T any](ctx context.Context, jobs []T, workerCount int, fn func(context.Context, T))

func SetIn

func SetIn(data any, path string, value any) error

func Subscribe

func Subscribe[T any](bus *EventBus, handler Handler[T])

func ToAnySlice

func ToAnySlice[T any](in []T) []any

func ToPtr

func ToPtr[T comparable](v T) *T

func Unzip2

func Unzip2[A, B any](tuples []Tuple2[A, B]) ([]A, []B)

func Unzip3

func Unzip3[A, B, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C)

func Unzip4

func Unzip4[A, B, C, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D)

Types

type Entry

type Entry[K comparable, V any] struct {
	Key   K
	Value V
}

type EventBus

type EventBus struct {
	// contains filtered or unexported fields
}

func NewEventBus

func NewEventBus() *EventBus

type Handler

type Handler[T any] func(context.Context, T)

type HandlerFunc

type HandlerFunc[T any] func(ctx context.Context, job T, logger *slog.Logger)

type Number

type Number interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64
}

Number is a type constraint that permits all integer and floating-point types.

type Tuple2

type Tuple2[A, B any] struct {
	A A
	B B
}

func CrossJoin2

func CrossJoin2[A, B any](listA []A, listB []B) []Tuple2[A, B]

func T2

func T2[A, B any](a A, b B) Tuple2[A, B]

func Zip2

func Zip2[A, B any](a []A, b []B) []Tuple2[A, B]

func (Tuple2[A, B]) Unpack

func (t Tuple2[A, B]) Unpack() (A, B)

type Tuple3

type Tuple3[A, B, C any] struct {
	A A
	B B
	C C
}

func CrossJoin3

func CrossJoin3[A, B, C any](listA []A, listB []B, listC []C) []Tuple3[A, B, C]

func T3

func T3[A, B, C any](a A, b B, c C) Tuple3[A, B, C]

func Zip3

func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C]

func (Tuple3[A, B, C]) Unpack

func (t Tuple3[A, B, C]) Unpack() (A, B, C)

type Tuple4

type Tuple4[A, B, C, D any] struct {
	A A
	B B
	C C
	D D
}

func CrossJoin4

func CrossJoin4[A, B, C, D any](listA []A, listB []B, listC []C, listD []D) []Tuple4[A, B, C, D]

func T4

func T4[A, B, C, D any](a A, b B, c C, d D) Tuple4[A, B, C, D]

func Zip4

func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D]

func (Tuple4[A, B, C, D]) Unpack

func (t Tuple4[A, B, C, D]) Unpack() (A, B, C, D)

type WorkerPool

type WorkerPool[T any] struct {
	// contains filtered or unexported fields
}

WorkerPool manages a pool of goroutines to process jobs concurrently.

func NewWorkerPool

func NewWorkerPool[T any](numWorkers int, jobs <-chan T, handler HandlerFunc[T], logger *slog.Logger) *WorkerPool[T]

NewWorkerPool creates a new worker pool.

func (*WorkerPool[T]) Run

func (p *WorkerPool[T]) Run(ctx context.Context)

Run starts the workers and blocks until jobs channel been closed

Jump to

Keyboard shortcuts

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