Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go

Type constraints, generic interfaces, goroutines, channels, select, contexts, and error handling.

mdBook tree-sitter
package pipeline

import (
	"context"
	"errors"
	"sync"
)

type Item interface {
	Key() string
}

type Store[T Item] interface {
	Load(context.Context, string) (T, error)
	Save(context.Context, T) error
}

type Result[T any] struct {
	Value T
	Err   error
}

func Map[A, B any](
	ctx context.Context,
	input <-chan A,
	workers int,
	fn func(context.Context, A) (B, error),
) <-chan Result[B] {
	output := make(chan Result[B])
	var group sync.WaitGroup

	group.Add(workers)
	for range workers {
		go func() {
			defer group.Done()
			for value := range input {
				item, err := fn(ctx, value)
				select {
				case output <- Result[B]{
					Value: item,
					Err:   err,
				}:
				case <-ctx.Done():
					return
				}
			}
		}()
	}

	go func() {
		group.Wait()
		close(output)
	}()
	return output
}

func Collect[T any](
	ctx context.Context,
	input <-chan Result[T],
) ([]T, error) {
	var values []T
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case result, ok := <-input:
			if !ok {
				return values, nil
			}
			if result.Err != nil {
				return nil, errors.Join(
					result.Err,
					ctx.Err(),
				)
			}
			values = append(values, result.Value)
		}
	}
}
package pipeline

import (
	"context"
	"errors"
	"sync"
)

type Item interface {
	Key() string
}

type Store[T Item] interface {
	Load(context.Context, string) (T, error)
	Save(context.Context, T) error
}

type Result[T any] struct {
	Value T
	Err   error
}

func Map[A, B any](
	ctx context.Context,
	input <-chan A,
	workers int,
	fn func(context.Context, A) (B, error),
) <-chan Result[B] {
	output := make(chan Result[B])
	var group sync.WaitGroup

	group.Add(workers)
	for range workers {
		go func() {
			defer group.Done()
			for value := range input {
				item, err := fn(ctx, value)
				select {
				case output <- Result[B]{
					Value: item,
					Err:   err,
				}:
				case <-ctx.Done():
					return
				}
			}
		}()
	}

	go func() {
		group.Wait()
		close(output)
	}()
	return output
}

func Collect[T any](
	ctx context.Context,
	input <-chan Result[T],
) ([]T, error) {
	var values []T
	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case result, ok := <-input:
			if !ok {
				return values, nil
			}
			if result.Err != nil {
				return nil, errors.Join(
					result.Err,
					ctx.Err(),
				)
			}
			values = append(values, result.Value)
		}
	}
}