A modern, robust, and type-safe collection of generic utilities for Go, designed to solve common problems with elegant, high-performance APIs.
Status: Stable - This project is production-ready and follows semantic versioning.
go get github.com/goexts/generic@latestFor a complete guide, API reference, and usage examples for all packages, please visit the official Go documentation:
pkg.go.dev/github.com/goexts/generic
This library provides a rich set of independent, generic packages:
cast: Safe, generic type-casting functions.cmp: Generic comparison functions for sorting and ordering.cond: Ternary-like conditional functions.configure: A powerful implementation of the Functional Options Pattern.maps: A suite of generic functions for common map operations (adapter forx/exp/maps).must: Panic-on-error wrappers for cleaner initialization code.promise: A generic, JavaScript-likePromiseimplementation for managing asynchronous operations.ptr: Helper functions for creating pointers from literal values.res: A generic, Rust-inspiredResult[T]type for expressive error handling.set: Stateless, slice-based set operations.slices: A comprehensive suite of functions for slice operations.strings: A collection of functions for string manipulation (adapter for the standardstringspackage).
The configure package provides a robust and type-safe implementation of the Functional Options Pattern. This pattern is ideal for creating complex objects with multiple optional parameters in a clean and readable way.
This demonstrates the core pattern: defining options and applying them to a default configuration. Note that configure.Apply expects a slice of options.
package main
import (
"fmt"
"net/http"
"time"
"github.com/goexts/generic/configure"
)
// ClientConfig holds the configuration for our http.Client.
type ClientConfig struct {
Timeout time.Duration
Transport http.RoundTripper
}
// Option defines the functional option type for our configuration.
type Option = configure.Option[ClientConfig]
// WithTimeout returns an option to set the client timeout.
func WithTimeout(d time.Duration) Option {
return func(c *ClientConfig) {
c.Timeout = d
}
}
// WithTransport returns an option to set the client's transport.
func WithTransport(rt http.RoundTripper) Option {
return func(c *ClientConfig) {
c.Transport = rt
}
}
// NewClient creates a new http.Client with default settings,
// then applies the provided options.
func NewClient(opts ...Option) *http.Client {
// 1. Start with a default configuration.
config := &ClientConfig{
Timeout: 10 * time.Second,
Transport: http.DefaultTransport,
}
// 2. Apply any user-provided options over the defaults.
// configure.Apply modifies the config in-place and returns it.
configure.Apply(config, opts)
// 3. The final, configured object is now ready to be used.
return &http.Client{
Timeout: config.Timeout,
Transport: config.Transport,
}
}
func main() {
// Create a client with default settings (no options).
defaultClient := NewClient()
fmt.Printf("Default client timeout: %s\n", defaultClient.Timeout)
// Create a client with a custom timeout, overriding the default.
// Pass options as variadic arguments.
customClient := NewClient(WithTimeout(30 * time.Second))
fmt.Printf("Custom client timeout: %s\n", customClient.Timeout)
// If you have individual options, you can pass them directly:
// anotherClient := NewClient(WithTimeout(20 * time.Second), WithTransport(&http.Transport{}))
}The Builder provides a fluent interface for collecting options incrementally, which is useful when options are gathered from various sources or in multiple stages. It also supports setting a base configuration.
package main
import (
"fmt"
"net/http"
"time"
"github.com/goexts/generic/configure"
)
// ClientConfig holds the configuration for our http.Client.
type ClientConfig struct {
Timeout time.Duration
Transport http.RoundTripper
EnableTracing bool // Added to demonstrate AddWhen's optIfFalse
}
// Option defines the functional option type for our configuration.
type Option = configure.Option[ClientConfig]
// WithTimeout returns an option to set the client timeout.
func WithTimeout(d time.Duration) Option {
return func(c *ClientConfig) {
c.Timeout = d
}
}
// WithTransport returns an option to set the client's transport.
func WithTransport(rt http.RoundTripper) Option {
return func(c *ClientConfig) {
c.Transport = rt
}
}
// WithTracing enables or disables tracing.
func WithTracing(enable bool) Option {
return func(c *ClientConfig) {
c.EnableTracing = enable
}
}
// NewClient is the factory function for Compile.
// It takes the final, fully-built configuration and creates the product (*http.Client).
func NewClient(config *ClientConfig) (*http.Client, error) {
// In a real scenario, you might use config.EnableTracing here
// to configure the http.Client or a wrapper around it.
return &http.Client{
Timeout: config.Timeout,
Transport: config.Transport,
}, nil
}
func main() {
// Define a base configuration that can be reused.
baseConfig := &ClientConfig{
Timeout: 5 * time.Second,
Transport: http.DefaultTransport,
EnableTracing: false,
}
// Create a builder, passing the base configuration directly to NewBuilder.
builder := configure.NewBuilder[ClientConfig](baseConfig).
Add(WithTimeout(15 * time.Second)). // Overrides baseConfig.Timeout
// Use AddWhen with optIfTrue and optIfFalse
AddWhen(true, WithTracing(true), WithTracing(false))
// Compile the final product using the builder and a factory function.
// Note: configure.Compile now expects factory first, then builder.
client1, err := configure.Compile(NewClient, builder)
if err != nil {
panic(err)
}
fmt.Printf("Client 1 (Builder) timeout: %s\n", client1.Timeout)
// Demonstrate using Chain to group options before adding to builder
commonOptions := configure.Chain(
WithTransport(&http.Transport{}), // Custom transport
WithTimeout(20 * time.Second),
)
client2, err := configure.Compile(
NewClient,
configure.NewBuilder[ClientConfig](baseConfig).
Add(commonOptions). // Add chained options
// AddWhen with false condition, so optIfFalse (WithTracing(false)) will be applied
AddWhen(false, WithTracing(true), WithTracing(false)),
)
if err != nil {
panic(err)
}
fmt.Printf("Client 2 (Builder) transport type: %T, timeout: %s\n", client2.Transport, client2.Timeout)
// Example of using Builder directly as an option (implements ApplierE)
// This is useful if you want to apply a set of options defined by a builder
// to an existing config object or within another ApplyAny call.
existingConfig := &ClientConfig{Timeout: 1 * time.Second}
err = configure.NewBuilder[ClientConfig]().
Add(WithTimeout(30 * time.Second)).
Apply(existingConfig) // Apply builder's options to an existing config
if err != nil {
panic(err)
}
fmt.Printf("Existing config after builder.Apply: %s\n", existingConfig.Timeout)
}We welcome contributions! Please see our Contributing Guide for details.
This project is licensed under the MIT License. See the LICENSE file for details.