lang

package module
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2025 License: MIT Imports: 7 Imported by: 6

README

lang

Go Version GoDoc Build Coverage GoReport

Package lang provides useful generic one-liners to work with variables, slices and maps

go get -u github.com/maxbolgarin/lang

Overview

lang is a comprehensive utility library that brings functional programming concepts to Go. It leverages Go's generics to provide type-safe operations for collections, error handling, and data transformations. This library eliminates boilerplate code and makes your Go code more expressive and maintainable.

Table of Contents

Core Utilities

Pointer and Value Helpers

These functions help you work with pointers and provide safe defaults:

// Working with pointers
name := lang.Ptr("John")           // *string pointing to "John"
age := lang.Ptr(25)                // *int pointing to 25

// Safe pointer dereferencing
var nilPtr *string
value := lang.Deref(nilPtr)        // "" (zero value)
value = lang.Deref(lang.Ptr("Hi")) // "Hi"

// Choosing between values
config := lang.Check("", "default")     // "default" (first is empty)
timeout := lang.Check(0, 30)            // 30 (first is zero)
Conditional Operations

Clean conditional logic without if statements:

// Conditional values
message := lang.If(user.IsAdmin, "Admin Panel", "User Panel")

// Conditional function execution
lang.IfF(user.IsLoggedIn, func() {
    log.Println("User logged in")
}, func() {
    log.Println("Anonymous user")
})

// Execute function only if value is non-zero
lang.IfV(user.ID, func() {
    updateUserStats(user.ID)
})
String Operations

Powerful string manipulation and conversion:

// Convert anything to string with optional length limit
text := lang.String(12345)           // "12345"
short := lang.String("Hello World", 5) // "Hello"

// String truncation with ellipsis
truncated := lang.TruncateString("Long text here", 8, "...") // "Long tex..."

// Path manipulation
path := lang.GetWithSep("config", '/')  // "config/"
path = lang.GetWithSep("config/", '/')  // "config/" (unchanged)

Slice Operations

Transformation and Filtering
// Transform elements to different types
numbers := []int{1, 2, 3, 4, 5}
strings := lang.Convert(numbers, func(n int) string {
    return fmt.Sprintf("#%d", n)
})
// strings: ["#1", "#2", "#3", "#4", "#5"]

// Filter with complex conditions
users := []User{
    {Name: "Alice", Age: 25, Active: true},
    {Name: "Bob", Age: 17, Active: false},
    {Name: "Charlie", Age: 30, Active: true},
}

activeAdults := lang.Filter(users, func(u User) bool {
    return u.Age >= 18 && u.Active
})
// activeAdults: [Alice, Charlie]

// Transform with error handling
inputs := []string{"1", "2", "invalid", "4"}
numbers, err := lang.ConvertWithErr(inputs, func(s string) (int, error) {
    return strconv.Atoi(s)
})
// numbers: nil, err: strconv.Atoi: parsing "invalid": invalid syntax
Search and Analysis
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

// Find first match
first, found := lang.FindFirst(numbers, func(n int) bool {
    return n > 5
})
// first: 6, found: true

// Check if all/any elements satisfy condition
allPositive := lang.All(numbers, func(n int) bool { return n > 0 })  // true
hasEven := lang.Any(numbers, func(n int) bool { return n%2 == 0 })   // true

// Find indices
index := lang.IndexOf([]string{"a", "b", "c", "b"}, "b")     // 1
lastIndex := lang.LastIndexOf([]string{"a", "b", "c", "b"}, "b") // 3
Data Manipulation
// Remove duplicates while preserving order
unique := lang.Distinct([]int{1, 2, 2, 3, 1, 4}) // [1, 2, 3, 4]

// Set operations
set1 := []int{1, 2, 3, 4}
set2 := []int{3, 4, 5, 6}

intersection := lang.Intersect(set1, set2)  // [3, 4]
union := lang.Union(set1, set2)             // [1, 2, 3, 4, 5, 6]
difference := lang.Difference(set1, set2)   // [1, 2]

// Chunking and partitioning
chunks := lang.Chunk([]int{1, 2, 3, 4, 5, 6, 7}, 3) // [[1,2,3], [4,5,6], [7]]

evens, odds := lang.Partition([]int{1, 2, 3, 4, 5}, func(n int) bool {
    return n%2 == 0
})
// evens: [2, 4], odds: [1, 3, 5]
Advanced Operations
// Reduce (fold) operation
sum := lang.Reduce([]int{1, 2, 3, 4, 5}, 0, func(acc, n int) int {
    return acc + n
})
// sum: 15

// Take and skip
first3 := lang.Take([]int{1, 2, 3, 4, 5}, 3)  // [1, 2, 3]
after2 := lang.Skip([]int{1, 2, 3, 4, 5}, 2)   // [3, 4, 5]

// Flatten nested slices
nested := [][]int{{1, 2}, {3, 4}, {5, 6}}
flat := lang.Flatten(nested) // [1, 2, 3, 4, 5, 6]

// Reverse
reversed := lang.Reverse([]int{1, 2, 3, 4, 5}) // [5, 4, 3, 2, 1]

Map Operations

Creation and Transformation
// Create maps from slices
users := []User{
    {ID: 1, Name: "Alice", Email: "[email protected]"},
    {ID: 2, Name: "Bob", Email: "[email protected]"},
}

// Map by key function
usersByID := lang.SliceToMapByKey(users, func(u User) int {
    return u.ID
})
// usersByID: map[int]User{1: {ID: 1, Name: "Alice", ...}, 2: {ID: 2, Name: "Bob", ...}}

// Transform to different key-value pairs
emailMap := lang.SliceToMap(users, func(u User) (string, string) {
    return u.Name, u.Email
})
// emailMap: map[string]string{"Alice": "[email protected]", "Bob": "[email protected]"}

// Create from pairs
pairs := []string{"name", "John", "age", "25", "city", "NYC"}
config := lang.PairsToMap(pairs)
// config: map[string]string{"name": "John", "age": "25", "city": "NYC"}
Map Manipulation
ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 17}

// Filter maps
adults := lang.FilterMap(ages, func(name string, age int) bool {
    return age >= 18
})
// adults: map[string]int{"Alice": 25, "Bob": 30}

// Transform values
descriptions := lang.ConvertMap(ages, func(age int) string {
    return fmt.Sprintf("%d years old", age)
})
// descriptions: map[string]string{"Alice": "25 years old", "Bob": "30 years old", "Charlie": "17 years old"}

// Extract keys and values
names := lang.Keys(ages)                    // ["Alice", "Bob", "Charlie"]
adultNames := lang.KeysIf(ages, func(name string, age int) bool {
    return age >= 18
})                                         // ["Alice", "Bob"]
Grouping and Aggregation
// Group by category
products := []Product{
    {Name: "iPhone", Category: "Electronics", Price: 999},
    {Name: "iPad", Category: "Electronics", Price: 799},
    {Name: "Book", Category: "Literature", Price: 25},
    {Name: "Pen", Category: "Office", Price: 5},
}

byCategory := lang.GroupBy(products, func(p Product) string {
    return p.Category
})
// byCategory: map[string][]Product{
//   "Electronics": [{iPhone...}, {iPad...}],
//   "Literature": [{Book...}],
//   "Office": [{Pen...}],
// }

// Merge multiple maps
priceMap1 := map[string]int{"apple": 2, "banana": 1}
priceMap2 := map[string]int{"banana": 3, "orange": 4}
merged := lang.MergeMap(priceMap1, priceMap2)
// merged: map[string]int{"apple": 2, "banana": 3, "orange": 4}

Error Handling

Panic Recovery
// Automatic goroutine recovery with restart
logger := &MyLogger{}
lang.Go(logger, func() {
    // This goroutine will restart if it panics
    for {
        riskyOperation()
        time.Sleep(time.Second)
    }
})

// Function-level panic recovery
func riskyFunction() (err error) {
    defer func() {
        if lang.RecoverWithErr(&err) {
            // Panic was converted to error
        }
    }()
    
    // Code that might panic
    return nil
}

// Default value on panic
result := lang.DefaultIfPanic("safe default", func() string {
    return mightPanicFunction()
})
Error Utilities
// Wrap errors with context
err := someOperation()
if err != nil {
    return lang.Wrap(err, "failed to perform operation")
}

// Combine multiple errors
err1 := operation1()
err2 := operation2()
if combinedErr := lang.JoinErrors(err1, err2); combinedErr != nil {
    return combinedErr
}

Type Safety

Type Conversion and Checking
// Safe type conversion
var value any = "hello"
str := lang.Type[string](value)  // "hello"
num := lang.Type[int](value)     // 0 (zero value, conversion failed)

// Retry mechanism
result, err := lang.Retry(3, func() (string, error) {
    return callExternalAPI()
})

// Timeout handling
result, err := lang.RunWithTimeout(5*time.Second, func() (string, error) {
    return longRunningOperation()
})

Real-World Examples

Data Processing Pipeline
// Process user data with validation and transformation
func processUserData(rawData []map[string]interface{}) []UserProfile {
    // Convert raw data to structured format
    users := lang.Convert(rawData, func(raw map[string]interface{}) User {
        return User{
            ID:    lang.Type[int](raw["id"]),
            Name:  lang.Type[string](raw["name"]),
            Email: lang.Type[string](raw["email"]),
            Age:   lang.Type[int](raw["age"]),
        }
    })
    
    // Filter valid users
    validUsers := lang.Filter(users, func(u User) bool {
        return u.ID > 0 && u.Name != "" && strings.Contains(u.Email, "@")
    })
    
    // Group by age range
    byAgeGroup := lang.GroupBy(validUsers, func(u User) string {
        switch {
        case u.Age < 18:
            return "minor"
        case u.Age < 65:
            return "adult"
        default:
            return "senior"
        }
    })
    
    // Create user profiles
    profiles := lang.ConvertFromMap(byAgeGroup, func(group string, users []User) UserProfile {
        return UserProfile{
            AgeGroup: group,
            Count:    len(users),
            Users:    users,
        }
    })
    
    return profiles
}
Configuration Management
// Safe configuration handling
type Config struct {
    Database DatabaseConfig
    Server   ServerConfig
    Features map[string]bool
}

func loadConfig() Config {
    // Load with defaults
    return Config{
        Database: DatabaseConfig{
            Host:     lang.Check(os.Getenv("DB_HOST"), "localhost"),
            Port:     lang.Check(parsePort(os.Getenv("DB_PORT")), 5432),
            Timeout:  lang.Check(parseDuration(os.Getenv("DB_TIMEOUT")), 30*time.Second),
        },
        Server: ServerConfig{
            Port:    lang.Check(parsePort(os.Getenv("SERVER_PORT")), 8080),
            Workers: lang.Check(parseWorkers(os.Getenv("WORKERS")), 4),
        },
        Features: lang.PairsToMap(lang.NotEmpty(strings.Split(os.Getenv("FEATURES"), ","))),
    }
}
API Response Processing
// Process API responses with error handling
func processAPIResponse(responses []APIResponse) ([]ProcessedData, error) {
    // Filter successful responses
    successful := lang.Filter(responses, func(r APIResponse) bool {
        return r.Status == "success"
    })
    
    // Extract and process data
    data, err := lang.ConvertWithErr(successful, func(r APIResponse) (ProcessedData, error) {
        return ProcessedData{
            ID:        r.ID,
            Value:     r.Data.Value,
            Timestamp: time.Now(),
        }, nil
    })
    
    if err != nil {
        return nil, lang.Wrap(err, "failed to process API responses")
    }
    
    // Remove duplicates and sort
    unique := lang.Distinct(data)
    
    return unique, nil
}

Performance Notes

  • Memory Allocation: Most functions pre-allocate slices and maps with appropriate capacity to minimize allocations
  • Goroutine Safety: All functions are goroutine-safe for read operations. Map operations are not goroutine-safe for concurrent writes
  • Large Data: For very large datasets (>1M elements), consider processing in chunks using Chunk() function
  • Error Handling: Functions with error handling (ConvertWithErr, RecoverWithErr) have minimal overhead when no errors occur

Best Practices

Function Composition
// Chain operations for cleaner code
result := lang.Filter(
    lang.Convert(rawData, parseUser),
    func(u User) bool { return u.IsValid() },
)

// Use with method chaining
type Pipeline[T any] struct {
    data []T
}

func (p Pipeline[T]) Filter(fn func(T) bool) Pipeline[T] {
    return Pipeline[T]{lang.Filter(p.data, fn)}
}

func (p Pipeline[T]) Map(fn func(T) T) Pipeline[T] {
    return Pipeline[T]{lang.Map(p.data, fn)}
}
Error Handling Strategy
// Prefer error returns over panics
func safeDivide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Use panic recovery only for truly exceptional cases
func mustParseConfig(filename string) Config {
    defer func() {
        if lang.Recover(logger) {
            log.Fatal("Failed to parse critical configuration")
        }
    }()
    
    return parseConfig(filename)
}
Memory Management
// For large slices, use copy operations to avoid memory leaks
func processLargeSlice(input []LargeStruct) []Result {
    // Process only what you need
    relevant := lang.Filter(input, isRelevant)
    
    // Copy to avoid holding reference to original large slice
    return lang.Convert(lang.Copy(relevant), processStruct)
}

Comparison with Native Go

Without lang (traditional Go):

// Filtering
var evens []int
for _, n := range numbers {
    if n%2 == 0 {
        evens = append(evens, n)
    }
}

// Mapping
var doubled []int
for _, n := range numbers {
    doubled = append(doubled, n*2)
}

// Grouping
groups := make(map[string][]Person)
for _, person := range people {
    key := person.Department
    groups[key] = append(groups[key], person)
}

With lang (functional approach):

// Filtering
evens := lang.Filter(numbers, func(n int) bool { return n%2 == 0 })

// Mapping
doubled := lang.Map(numbers, func(n int) int { return n * 2 })

// Grouping
groups := lang.GroupBy(people, func(p Person) string { return p.Department })
  • lo: Another excellent functional programming library for Go
  • go-funk: Functional utilities using reflection
  • pie: Type-safe slice operations

Add functional programming power to your Go projects: Documentation

Documentation

Overview

Package lang provides useful generic oneliners to work with variables and pointers.

Index

Constants

This section is empty.

Variables

View Source
var ErrTimeout = errors.New("operation timed out")

Functions

func All added in v1.8.0

func All[T any](s []T, predicate func(T) bool) bool

All returns true if all elements in the slice satisfy the predicate.

allPositive := All([]int{1, 2, 3}, func(n int) bool { return n > 0 }) // allPositive == true

func Any added in v1.8.0

func Any[T any](s []T, predicate func(T) bool) bool

Any returns true if at least one element in the slice satisfies the predicate.

hasNegative := Any([]int{1, -2, 3}, func(n int) bool { return n < 0 }) // hasNegative == true

func AppendIfAll added in v1.6.0

func AppendIfAll[T comparable](s []T, v ...T) []T

AppendIfAll appends the value to the slice if it is not empty. All values must be different from zero to be appended.

b := []string{"foo", "bar"}
c := AppendIfAll(b, "foo")  // c == []string{"foo", "bar", "foo"}
d := AppendIfAll(b, "")     // d == []string{"foo", "bar"}
e := AppendIfAll(b, "foo", "")  // e == []string{"foo", "bar"}
f := AppendIfAll(b, "foo", "bar")  // f == []string{"foo", "bar", "foo", "bar"}

func AppendIfAny added in v1.6.0

func AppendIfAny[T comparable](s []T, v ...T) []T

AppendIfAny appends the value to the slice if it is not empty. Any value must be different from zero to be appended.

b := []string{"foo", "bar"}
c := AppendIfAny(b, "foo")  // c == []string{"foo", "bar", "foo"}
d := AppendIfAny(b, "")     // d == []string{"foo", "bar"}
e := AppendIfAny(b, "foo", "")  // e == []string{"foo", "bar", "foo"}
f := AppendIfAny(b, "foo", "bar")  // f == []string{"foo", "bar", "foo", "bar"}

func Check

func Check[T comparable](v1, v2 T) T

Check returns the first argument if it is not default, else returns the second one.

a := ""
b := "foo"
c := Check(a, b) // c == "foo"

func CheckIndex

func CheckIndex[T any](s []T, index int) (T, bool)

CheckIndex returns the value and true if the index is not out of bounds.

a := []int{1, 2, 3}
b, ok := CheckIndex(a, 2) // b == 3 && ok == true
c, ok := CheckIndex(a, 4) // c == 0 && ok == false

func CheckMap added in v1.2.0

func CheckMap[K comparable, V any](v1, v2 map[K]V) map[K]V

CheckMap returns the first argument if it is not empty, else returns the second one.

a := map[string]int{}
b := map[string]string{"foo": "bar"}
c := CheckMap(a, b)  // c == map[string]string{"foo": "bar"}

func CheckMapSingle added in v1.2.0

func CheckMapSingle[K comparable, V any](m map[K]V, k K, v V) map[K]V

CheckMapSingle returns the first argument if it is not empty, else returns the second one wrapped in a map.

a := nil
b := "foo"
c := CheckMapSingle(a, b)  // c == map[string]string{"foo": "bar"}

func CheckPtr

func CheckPtr[T any](v1 *T, v2 T) T

CheckPtr returns dereference of the first argument (pointer) if it is not nil, else returns the second one.

a := ""
b := "foo"
c := CheckPtr(&a, b)  // c == ""
d := CheckPtr(nil, b) // d == "foo"

func CheckPtrs added in v1.9.0

func CheckPtrs[T any](v1 *T, v2 *T) *T

CheckPtrs returns the first argument if it is not nil, else returns the second one.

a := ""
b := "foo"
c := CheckPtrs(&a, &b)  // c == &a
d := CheckPtrs(nil, &b) // d == &b

func CheckSlice added in v1.2.0

func CheckSlice[T any](v1, v2 []T) []T

CheckSlice returns the first argument if it is not empty, else returns the second one.

a := []int{}
b := []string{"foo", "bar"}
c := CheckSlice(a, b)  // c == []string{"foo", "bar"}

func CheckSliceSingle added in v1.2.0

func CheckSliceSingle[T any](s []T, v T) []T

CheckSliceSingle returns the first argument if it is not empty, else returns the second one wrapped in a slice.

a := nil
b := "foo"
c := CheckSliceSingle(a, b)  // c == []string{"foo"}

func CheckTime

func CheckTime(v1 time.Time, v2 time.Time) time.Time

CheckTime returns the first time if it is not zero, second one elsewhere.

a := time.Time{}
b := time.Now()
c := CheckTime(a, b)  // c == time.Now()

func Chunk added in v1.8.0

func Chunk[T any](s []T, size int) [][]T

Chunk splits a slice into chunks of the specified size (alias for SplitByChunkSize).

chunks := Chunk([]int{1, 2, 3, 4, 5}, 2) // chunks == [][]int{{1, 2}, {3, 4}, {5}}

func Compact added in v1.8.0

func Compact[T any](s []*T) []*T

Compact removes nil values from a slice of pointers or interfaces.

a, b, c := 1, 2, 3
ptrs := []*int{&a, nil, &b, nil, &c}
nonNil := Compact(ptrs) // nonNil == []*int{&a, &b, &c}

func Contains added in v1.8.0

func Contains[T comparable](s []T, element T) bool

Contains checks if a slice contains a specific element.

found := Contains([]int{1, 2, 3}, 2) // found == true

func ContainsFunc added in v1.8.0

func ContainsFunc[T any](s []T, predicate func(T) bool) bool

ContainsFunc checks if a slice contains an element that satisfies the predicate.

found := ContainsFunc([]int{1, 2, 3}, func(n int) bool { return n > 2 }) // found == true

func Convert

func Convert[T, K any](input []T, transform func(T) K) []K

Convert transforms each element of a slice to a different type using the provided function.

numbers := []int{1, 2, 3}
strings := Convert(numbers, func(n int) string { return strconv.Itoa(n) }) // strings == []string{"1", "2", "3"}

func ConvertFromMap added in v1.6.0

func ConvertFromMap[K comparable, T1, T2 any](input map[K]T1, transform func(K, T1) T2) []T2

ConvertFromMap transforms each key-value pair in a map into a slice element.

ages := map[string]int{"Alice": 25, "Bob": 30}
descriptions := ConvertFromMap(ages, func(name string, age int) string {
    return fmt.Sprintf("%s is %d years old", name, age)
}) // descriptions == []string{"Alice is 25 years old", "Bob is 30 years old"}

func ConvertFromMapWithErr added in v1.6.0

func ConvertFromMapWithErr[K comparable, T1, T2 any](input map[K]T1, transform func(K, T1) (T2, error)) ([]T2, error)

ConvertFromMapWithErr transforms each key-value pair in a map into a slice element. Returns an error if any transformation fails.

stringNums := map[string]string{"a": "1", "b": "invalid"}
numbers, err := ConvertFromMapWithErr(stringNums, func(key string, val string) (int, error) {
    return strconv.Atoi(val)
}) // numbers == nil, err != nil

func ConvertMap

func ConvertMap[K comparable, T1, T2 any](input map[K]T1, transform func(T1) T2) map[K]T2

ConvertMap transforms each value in a map using the provided function while preserving keys.

ages := map[string]int{"Alice": 25, "Bob": 30}
descriptions := ConvertMap(ages, func(age int) string {
    return fmt.Sprintf("%d years old", age)
}) // descriptions == map[string]string{"Alice": "25 years old", "Bob": "30 years old"}

func ConvertMapWithErr

func ConvertMapWithErr[K comparable, T1, T2 any](input map[K]T1, transform func(T1) (T2, error)) (map[K]T2, error)

ConvertMapWithErr transforms each value in a map using the provided function while preserving keys. Returns an error if any transformation fails.

stringNums := map[string]string{"a": "1", "b": "invalid"}
numbers, err := ConvertMapWithErr(stringNums, func(s string) (int, error) {
    return strconv.Atoi(s)
}) // numbers == nil, err != nil

func ConvertToMap added in v1.6.0

func ConvertToMap[T1 any, K comparable, T2 any](input []T1, transform func(T1) (K, T2)) map[K]T2

ConvertToMap transforms each element of a slice into a key-value pair for a map.

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
userMap := ConvertToMap(users, func(u User) (int, string) {
    return u.ID, u.Name
}) // userMap == map[int]string{1: "Alice", 2: "Bob"}

func ConvertToMapWithErr added in v1.6.0

func ConvertToMapWithErr[T1 any, K comparable, T2 any](input []T1, transform func(T1) (K, T2, error)) (map[K]T2, error)

ConvertToMapWithErr transforms each element of a slice into a key-value pair for a map. Returns an error if any transformation fails.

strings := []string{"1", "2", "invalid"}
numberMap, err := ConvertToMapWithErr(strings, func(s string) (string, int, error) {
    num, err := strconv.Atoi(s)
    return s, num, err
}) // numberMap == nil, err != nil

func ConvertValue added in v1.8.0

func ConvertValue[T, K any](v T, f func(T) K) K

ConvertValue converts a value to a constant type.

a := 1
b := ConvertValue(a, func(a int) string { return strconv.Itoa(a) }) // b == "1"

func ConvertWithErr

func ConvertWithErr[T, K any](input []T, transform func(T) (K, error)) ([]K, error)

ConvertWithErr transforms each element of a slice to a different type using the provided function. Returns an error if any transformation fails.

strings := []string{"1", "2", "invalid"}
numbers, err := ConvertWithErr(strings, func(s string) (int, error) {
    return strconv.Atoi(s)
}) // numbers == nil, err != nil

func Copy

func Copy[T any](input []T) []T

Copy creates a shallow copy of a slice.

original := []int{1, 2, 3}
copied := Copy(original) // copied == []int{1, 2, 3}
original[0] = 99         // copied[0] is still 1

func CopyMap added in v1.1.0

func CopyMap[K comparable, T any](input map[K]T) map[K]T

CopyMap creates a shallow copy of a map.

original := map[string]int{"a": 1, "b": 2}
copied := CopyMap(original) // copied == map[string]int{"a": 1, "b": 2}
original["a"] = 99          // copied["a"] is still 1

func DefaultIfPanic added in v1.8.0

func DefaultIfPanic[T any](defaultValue T, f func() T) (result T)

DefaultIfPanic executes a function and returns its result, or returns a default value if the function panics. This is useful for operations that might panic but you want to provide a fallback value.

result := DefaultIfPanic("default", func() string {
    return riskyOperation() // might panic
}) // result == "default" if riskyOperation panics, otherwise the actual result

func Deref

func Deref[T any](v *T) T

Deref returns dereference of the pointer if it is not nil, else returns the default value.

var a *int
b := 123
c := Deref(a) // c == 0
d := Deref(&b) // d == 123

func Difference added in v1.8.0

func Difference[T comparable](a, b []T) []T

Difference returns elements that exist in the first slice but not in the second.

diff := Difference([]int{1, 2, 3, 4}, []int{2, 4}) // diff == []int{1, 3}

func Distinct added in v1.8.0

func Distinct[T comparable](s []T) []T

Distinct returns a new slice with duplicate elements removed, preserving order.

unique := Distinct([]int{1, 2, 2, 3, 1, 4}) // unique == []int{1, 2, 3, 4}

func Filter

func Filter[T any](input []T, filter func(T) bool) []T

Filter returns a new slice containing only the elements that satisfy the filter function.

numbers := []int{1, 2, 3, 4, 5, 6}
evens := Filter(numbers, func(n int) bool { return n%2 == 0 }) // evens == []int{2, 4, 6}

func FilterMap added in v1.5.0

func FilterMap[K comparable, T any](input map[K]T, filter func(K, T) bool) map[K]T

FilterMap returns a new map containing only the key-value pairs that satisfy the filter function.

ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 17}
adults := FilterMap(ages, func(name string, age int) bool {
    return age >= 18
}) // adults == map[string]int{"Alice": 25, "Bob": 30}

func FindFirst added in v1.8.0

func FindFirst[T any](s []T, predicate func(T) bool) (T, bool)

FindFirst returns the first element in a slice that satisfies a predicate. Returns zero value and false if no element is found.

nums := []int{1, 2, 3, 4, 5}
first, found := FindFirst(nums, func(n int) bool { return n > 3 }) // first == 4, found == true

func First

func First[T any](s []T) T

First returns the first element of the slice if it is not empty.

var a []int
b := []string{"foo", "bar"}
c := First(a)  // c == 0
d := First(b)  // d == "foo"

func Flatten added in v1.8.0

func Flatten[T any](s [][]T) []T

Flatten transforms a slice of slices into a single slice with all elements.

flat := Flatten([][]int{{1, 2}, {3, 4}}) // flat == []int{1, 2, 3, 4}

func ForEach added in v1.8.0

func ForEach[T any](s []T, f func(T))

ForEach executes a function for each element in a slice.

sum := 0
ForEach([]int{1, 2, 3}, func(n int) { sum += n })
// sum == 6

func GetWithSep

func GetWithSep(value string, sep byte) string

GetWithSep returns the value (first argument) with the separator (second argument), if the separator does not exist in the last index of the value.

a := GetWithSep("config", '/')  // a == "config/"
b := GetWithSep("config/", '/') // b == "config/"
c := GetWithSep("config/files", '/') // b == "config/files/"

func Go added in v1.4.0

func Go(l Logger, f func())

Go runs a goroutine with automatic panic recovery and restart capability. If the goroutine panics, it logs the stack trace and restarts the goroutine. It includes rate limiting to prevent excessive restarts (max 60 per minute). Use this when you want automatic recovery and restart after panics.

// Example usage:
Go(logger, func() {
    // Your goroutine code here
    for {
        // Do work that might panic
        time.Sleep(time.Second)
    }
})

func GroupBy added in v1.8.0

func GroupBy[T any, K comparable](s []T, keyFn func(T) K) map[K][]T

GroupBy groups slice elements by a key generated from each element.

people := []struct{Name string; Age int}{{"Alice", 25}, {"Bob", 30}, {"Charlie", 25}}
byAge := GroupBy(people, func(p struct{Name string; Age int}) int { return p.Age })
// byAge == map[int][]struct{Name string; Age int}{
//   25: {{"Alice", 25}, {"Charlie", 25}},
//   30: {{"Bob", 30}},
// }

func If

func If[T any](cond bool, ifTrue, ifFalse T) T

If returns ifTrue if condition is true, otherwise it returns ifFalse.

a := If(true, 1, 2)  // a == 1
b := If(false, 1, 2) // b == 2

func IfF added in v1.7.0

func IfF(cond bool, f func(), fFalse ...func())

IfF executes the function if the condition is true.

IfF(true, func() { println("foo") }) // foo IfF(false, func() { println("foo") }) // nothing IfF(false, func() { println("foo") }, func() { println("bar") }) // bar

func IfV added in v1.7.0

func IfV[T comparable](v T, f func(), fFalse ...func())

IfV executes the function if the value is not zero.

a := IfV(1, func() { println("foo") })  // foo
b := IfV(0, func() { println("foo") })  // nothing
c := IfV(0, func() { println("foo") }, func() { println("bar") }) // bar

func Index

func Index[T any](s []T, index int) T

Index returns the value if the index is not out of bounds.

a := []int{1, 2, 3}
b := Index(a, 2) // b == 3
c := Index(a, 4) // c == 0

func IndexOf added in v1.8.0

func IndexOf[T comparable](s []T, element T) int

IndexOf returns the index of the first occurrence of an element in a slice, or -1 if not found.

idx := IndexOf([]string{"a", "b", "c"}, "b") // idx == 1

func Intersect added in v1.8.0

func Intersect[T comparable](a, b []T) []T

Intersect returns elements that exist in both slices.

common := Intersect([]int{1, 2, 3}, []int{2, 3, 4}) // common == []int{2, 3}

func IsFound added in v1.3.0

func IsFound[T comparable](s []T, v T) bool

IsFound returns if the value is in the slice.

a := []int{1, 2, 3}
b := IsFound(a, 3)  // b == true
c := IsFound(a, 4)  // c == false

func JoinErrors added in v1.8.0

func JoinErrors(errs ...error) error

JoinErrors combines multiple errors into a single error.

err1 := SomeFunction1()
err2 := SomeFunction2()
if err := JoinErrors(err1, err2); err != nil {
    return err
}

func Keys added in v1.5.0

func Keys[K comparable, T any](input map[K]T) []K

Keys returns a slice containing all keys from a map.

mapping := map[string]int{"a": 1, "b": 2, "c": 3}
keys := Keys(mapping) // keys == []string{"a", "b", "c"} (order may vary)

func KeysIf added in v1.5.0

func KeysIf[K comparable, T any](input map[K]T, filter func(K, T) bool) []K

KeysIf returns a slice containing keys from a map that satisfy the filter function.

ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 17}
adultNames := KeysIf(ages, func(name string, age int) bool {
    return age >= 18
}) // adultNames == []string{"Alice", "Bob"} (order may vary)

func LastIndexOf added in v1.8.0

func LastIndexOf[T comparable](s []T, element T) int

LastIndexOf returns the index of the last occurrence of an element in a slice, or -1 if not found.

idx := LastIndexOf([]string{"a", "b", "c", "b"}, "b") // idx == 3

func Map

func Map[T any](input []T, transform func(T) T) []T

Map transforms each element of a slice using the provided function and returns a new slice.

numbers := []int{1, 2, 3}
doubled := Map(numbers, func(n int) int { return n * 2 }) // doubled == []int{2, 4, 6}

func Mapping added in v1.8.0

func Mapping[T any, K comparable](input []T, key func(T) K) map[K]T

Mapping is an alias for SliceToMapByKey that creates a map by using a key function.

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
userMap := Mapping(users, func(u User) int {
    return u.ID
}) // userMap == map[int]User{1: {ID: 1, Name: "Alice"}, 2: {ID: 2, Name: "Bob"}}

func MaxLen added in v1.6.0

func MaxLen[T any](s []T, targetMax int) []T

MaxLen returns the slice with the maximum length.

a := []int{1, 2, 3}
b := MaxLen(a, 2) // b == [1, 2]

func MergeMap added in v1.8.0

func MergeMap[K comparable, V any](maps ...map[K]V) map[K]V

MergeMap merges multiple maps into a single map. In case of key conflicts, values from later maps overwrite earlier ones.

merged := MergeMap(
    map[string]int{"a": 1, "b": 2},
    map[string]int{"b": 3, "c": 4},
) // merged == map[string]int{"a": 1, "b": 3, "c": 4}

func NotEmpty added in v1.8.0

func NotEmpty[T comparable](input []T) []T

NotEmpty is an alias for WithoutEmpty that returns a new slice with all zero values removed.

mixed := []string{"hello", "", "world", ""}
nonEmpty := NotEmpty(mixed) // nonEmpty == []string{"hello", "world"}

func NotEmptyMap added in v1.8.0

func NotEmptyMap[K, T comparable](input map[K]T) map[K]T

NotEmptyMap returns a new map with all entries that have zero-value keys or values removed.

mapping := map[string]int{"": 1, "a": 0, "b": 2}
filtered := NotEmptyMap(mapping) // filtered == map[string]int{"b": 2}

func PairsToMap

func PairsToMap[T comparable](input []T) map[T]T

PairsToMap transforms a slice with pairs of elements into a map. The first element of each pair becomes the key, and the second becomes the value. If the slice has an odd number of elements, the last element is ignored.

pairs := []string{"key1", "value1", "key2", "value2", "key3"}
mapping := PairsToMap(pairs) // mapping == map[string]string{"key1": "value1", "key2": "value2"}

func Partition added in v1.8.0

func Partition[T any](s []T, predicate func(T) bool) ([]T, []T)

Partition splits a slice into two slices based on a predicate function. The first slice contains elements that satisfy the predicate, the second contains those that don't.

evens, odds := Partition([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 })
// evens == []int{2, 4}, odds == []int{1, 3, 5}

func Ptr

func Ptr[T any](v T) *T

Ptr returns a pointer to a provided argument. It is useful to get an address of a literal.

// a := &"some literal" // won't compile
a := Ptr("some literal")

func Recover added in v1.4.0

func Recover(l Logger) bool

Recover should be used with defer to recover from panics and log the stack trace. It returns true if a panic was recovered, false otherwise. Use this when you want to handle panics gracefully without stopping execution.

func riskyOperation() {
    defer func() {
        if Recover(logger) {
            // Handle the panic case
        }
    }()
    // Code that might panic
}

func RecoverWithErr added in v1.4.0

func RecoverWithErr(outerError *error) bool

RecoverWithErr should be used with defer to recover from panics and convert them to errors. It returns true if a panic was recovered, false otherwise. The panic is converted to an error and stored in the provided error pointer. Use this when you want to convert panics to errors without logging.

func riskyOperation() (err error) {
    defer func() {
        if RecoverWithErr(&err) {
            // Panic was converted to error
        }
    }()
    // Code that might panic
    return nil
}

func RecoverWithErrAndStack added in v1.4.0

func RecoverWithErrAndStack(l Logger, outerError *error) bool

RecoverWithErrAndStack should be used with defer to recover from panics, convert them to errors, and log the stack trace. It returns true if a panic was recovered, false otherwise. The panic is converted to an error and stored in the provided error pointer, and the stack trace is logged. Use this when you want to convert panics to errors and also log the stack trace.

func riskyOperation() (err error) {
    defer func() {
        if RecoverWithErrAndStack(logger, &err) {
            // Panic was converted to error and logged
        }
    }()
    // Code that might panic
    return nil
}

func RecoverWithHandler added in v1.4.0

func RecoverWithHandler(handler func(err any)) bool

RecoverWithHandler should be used with defer to recover from panics and call a custom handler function. It returns true if a panic was recovered, false otherwise. The handler function is called with the panic value if a panic occurs. Use this when you want custom handling of panics.

func riskyOperation() {
    defer func() {
        if RecoverWithHandler(func(panicValue any) {
            fmt.Printf("Panic recovered: %v\n", panicValue)
        }) {
            // Panic was handled
        }
    }()
    // Code that might panic
}

func Reduce added in v1.8.0

func Reduce[T, K any](s []T, initial K, f func(K, T) K) K

Reduce applies a function to each element in a slice to reduce it to a single value.

nums := []int{1, 2, 3, 4}
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n }) // sum == 10

func Retry added in v1.8.0

func Retry[T any](maxAttempts int, f func() (T, error)) (T, error)

Retry attempts to execute a function until it succeeds or reaches max attempts.

result, err := Retry(3, func() (string, error) {
    return CallExternalAPI()
})

func Reverse added in v1.8.0

func Reverse[T any](s []T) []T

Reverse returns a new slice with elements in reverse order.

reversed := Reverse([]int{1, 2, 3}) // reversed == []int{3, 2, 1}

func RunWithTimeout added in v1.8.0

func RunWithTimeout[T any](timeout time.Duration, f func() (T, error)) (T, error)

RunWithTimeout runs a function with a timeout.

result, err := RunWithTimeout(time.Second, func() (string, error) {
    return SlowOperation()
})

func S added in v1.9.0

func S(s any, maxLenRaw ...int) string

S is a shortcut for String.

func Skip added in v1.8.0

func Skip[T any](s []T, n int) []T

Skip returns a slice without the first n elements. If n is greater than the length of the slice, an empty slice is returned.

rest := Skip([]int{1, 2, 3, 4, 5}, 2) // rest == []int{3, 4, 5}

func Slice added in v1.9.0

func Slice[T any](s any, maxLenRaw ...int) []T

Slice returns a slice of the given type. If the input is a slice, it is truncated to the given length. If the input is a single value, it is returned as a slice of length 1. If the input is not a slice or a single value, it is returned as nil.

a := []int{1, 2, 3}
b := Slice(a, 2) // b == []int{1, 2}

func SliceToMap

func SliceToMap[T any, K comparable, V any](input []T, transform func(T) (K, V)) map[K]V

SliceToMap creates a map by transforming each element of a slice into a key-value pair. The transform function should return a key and value for each element.

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
userMap := SliceToMap(users, func(u User) (int, string) {
    return u.ID, u.Name
}) // userMap == map[int]string{1: "Alice", 2: "Bob"}

func SliceToMapByKey

func SliceToMapByKey[T any, K comparable](input []T, key func(T) K) map[K]T

SliceToMapByKey creates a map by using a key function to generate keys from slice elements. The elements themselves become the values in the resulting map.

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
userMap := SliceToMapByKey(users, func(u User) int {
    return u.ID
}) // userMap == map[int]User{1: {ID: 1, Name: "Alice"}, 2: {ID: 2, Name: "Bob"}}

func SplitByChunkSize added in v1.8.0

func SplitByChunkSize[T any](items []T, chunkSize int) [][]T

SplitByChunkSize splits a slice into chunks of the specified size. If chunkSize is less than 1, it will be treated as 1.

items := []int{1, 2, 3, 4, 5, 6, 7}
chunks := SplitByChunkSize(items, 3) // chunks == [][]int{{1, 2, 3}, {4, 5, 6}, {7}}

func String added in v1.9.0

func String(s any, maxLenRaw ...int) string

String returns the string representation of the value with the optional maximum length.

a := String("Hello") // a == "Hello"
b := String(123) // b == "123"
c := String(123.456) // c == "123.456"
d := String(true) // d == "true"
e := String(time.Now()) // e == "2021-01-01T00:00:00Z"
f := String([]byte("Hello, world!")) // f == "Hello, world!"
g := String([]byte("Hello, world!"), 5) // g == "Hello"
h := String(nil, 5) // h == ""
i := String(nil, 0) // i == ""
j := String(nil, -1) // j == ""

func Take added in v1.8.0

func Take[T any](s []T, n int) []T

Take returns a slice with the first n elements. If n is greater than the length of the slice, the entire slice is returned.

first3 := Take([]int{1, 2, 3, 4, 5}, 3) // first3 == []int{1, 2, 3}

func TruncateSlice added in v1.9.0

func TruncateSlice[T any](s []T, maxLen int) []T

TruncateSlice truncates a slice to a maximum length. It is not change capacity of the slice, so items will be still in the underlying array.

a := []int{1, 2, 3}
b := TruncateSlice(a, 2) // b == []int{1, 2}

func TruncateSliceWithCopy added in v1.9.0

func TruncateSliceWithCopy[T any](s []T, maxLen int) []T

TruncateSliceWithCopy truncates a slice to a maximum length and returns a new slice. Old slice will be garbage collected if there are no references to it.

a := []int{1, 2, 3}
b := TruncateSliceWithCopy(a, 2) // b == []int{1, 2}

func TruncateString added in v1.8.0

func TruncateString(s string, maxLen int, ellipsis ...string) string

TruncateString truncates a string to a maximum length and adds an ellipsis if necessary.

s := "Hello, world!"
t := TruncateString(s, 5, "...") // t == "Hello..."

func Type added in v1.9.0

func Type[Target any](s any) Target

Type returns the value of the target type if the value is not nil.

a := Type[string](123) // a == ""
b := Type[string](nil) // b == ""
c := Type[string]("foo") // c == "foo"
d := Type[someEnum]("foo") // d == "" (type someEnum string) !!!
var v any = someEnum("foo")
e := Type[someEnum](v) // e == "foo" e.Type() == someEnum

func Union added in v1.8.0

func Union[T comparable](slices ...[]T) []T

Union returns a slice with unique elements from all input slices.

all := Union([]int{1, 2, 3}, []int{2, 3, 4}, []int{4, 5, 6}) // all == []int{1, 2, 3, 4, 5, 6}

func Values added in v1.5.0

func Values[K comparable, T any](input map[K]T) []T

Values returns a slice containing all values from a map.

mapping := map[string]int{"a": 1, "b": 2, "c": 3}
values := Values(mapping) // values == []int{1, 2, 3} (order may vary)

func ValuesIf added in v1.5.0

func ValuesIf[K comparable, T any](input map[K]T, filter func(K, T) bool) []T

ValuesIf returns a slice containing values from a map that satisfy the filter function.

ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 17}
adultAges := ValuesIf(ages, func(name string, age int) bool {
    return age >= 18
}) // adultAges == []int{25, 30} (order may vary)

func WithoutEmpty added in v1.3.0

func WithoutEmpty[T comparable](input []T) []T

WithoutEmpty returns a new slice with all zero values removed.

mixed := []string{"hello", "", "world", ""}
nonEmpty := WithoutEmpty(mixed) // nonEmpty == []string{"hello", "world"}

func WithoutEmptyKeys added in v1.3.0

func WithoutEmptyKeys[K comparable, T any](input map[K]T) map[K]T

WithoutEmptyKeys returns a new map with all entries that have zero-value keys removed.

mapping := map[string]int{"": 1, "a": 2, "b": 3}
filtered := WithoutEmptyKeys(mapping) // filtered == map[string]int{"a": 2, "b": 3}

func WithoutEmptyValues added in v1.3.0

func WithoutEmptyValues[K, T comparable](input map[K]T) map[K]T

WithoutEmptyValues returns a new map with all entries that have zero-value values removed.

mapping := map[string]int{"a": 0, "b": 2, "c": 3}
filtered := WithoutEmptyValues(mapping) // filtered == map[string]int{"b": 2, "c": 3}

func Wrap added in v1.8.0

func Wrap(err error, message string) error

WrapError adds a context message to an error.

err := SomeFunction()
if err != nil {
    return Wrap(err, "failed to execute SomeFunction")
}

func ZipToMap added in v1.8.0

func ZipToMap[K comparable, V any](keys []K, values []V) map[K]V

ZipToMap creates a map from two slices, using the first slice for keys and the second for values. If the slices have different lengths, the extra elements from the longer slice are ignored.

keys := []string{"a", "b", "c"}
values := []int{1, 2, 3}
mapping := ZipToMap(keys, values) // mapping == map[string]int{"a": 1, "b": 2, "c": 3}

Types

type Logger added in v1.4.0

type Logger interface {
	Error(msg string, args ...any)
}

Logger is an interface for logging panic stack traces and errors. It provides a method to log error messages with optional arguments.

Jump to

Keyboard shortcuts

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