
go get github.com/enetx/g
Requires Go 1.27+ (generic methods: Map, Then, Fold, Zip and friends change element types right in the chain — no more package-level Transform* workarounds)
package main
import (
"fmt"
"github.com/enetx/g"
)
func main() {
// Slice with functional operations
g.SliceOf(1, 2, 3, 4, 5).
Iter().
Filter(func(x int) bool { return x%2 == 0 }).
Map(func(x int) int { return x * x }).
Collect().
Println() // Slice[4, 16]
// Safe map access with Option
m := g.NewMap[string, int]()
m.Insert("key", 42)
value := m.Get("key").UnwrapOr(0)
fmt.Println(value) // 42
// Result for error handling
result := g.String("123").TryInt()
if result.IsOk() {
fmt.Println(result.Unwrap()) // 123
}
}
| Core | Collections | Sync | Types |
|---|---|---|---|
| Option | Slice | Mutex | String |
| Result | Map | RwLock | Int/Float |
| Iterators | Set | Pool | Bytes |
| Heap | File/Dir | ||
| Deque |
Safe nullable values: Some(value) or None.
opt := g.Some(42) // Some(42)
none := g.None[int]() // None
opt.IsSome() // true
opt.Unwrap() // 42 (panics if None)
opt.UnwrapOr(0) // 42 (returns default if None)
opt.UnwrapOrDefault() // 42 (returns zero value if None)
// From map lookup
v, ok := myMap[key]
opt := g.OptionOf(v, ok)
// Chaining
g.Some(5).Then(func(x int) g.Option[int] {
return g.Some(x * 2)
}) // Some(10)
All Option Methods
| Method | Description |
|---|---|
IsSome() / IsNone() |
Presence checks |
IsSomeAnd(pred) / IsNoneOr(pred) |
Presence combined with a predicate |
Some() |
Returns value (zero value if None — check first) |
Unwrap() / Expect(msg) |
Returns value, panics if None |
UnwrapOr(default) / UnwrapOrDefault() |
Returns value or fallback |
Map(fn) |
Transforms the value, may change its type |
MapOr(def, fn) / MapOrElse(defFn, fn) |
Map or fall back to a default |
Then(fn) |
Chains a fallible transformation (and_then) |
Filter(pred) |
Returns None if predicate fails |
Or(other) / OrElse(fn) |
Fallback Option |
Inspect(fn) |
Side effect on Some, returns self |
OkOr(err) / OkOrElse(fn) |
Converts to Result |
Take() / Replace(v) / Insert(v) |
In-place mutation |
Option() |
Returns (value, bool) |
JSON: Some(v) ⇄ value, None ⇄ null (encoding/json/v2 native, v1 compatible).
Success (Ok) or failure (Err) with error.
ok := g.Ok(42) // Ok(42)
err := g.Err[int](errors.New("failed")) // Err
ok.IsOk() // true
ok.Unwrap() // 42
err.UnwrapOr(0) // 0
// From standard (value, error) pattern
result := g.ResultOf(strconv.Atoi("42")) // Ok(42)
result := g.String("42").TryInt() // Ok(42)
// Chaining
g.Ok(10).Then(func(x int) g.Result[int] {
return g.Ok(x * 2)
}) // Ok(20)
// Convert to Option (discards error)
opt := result.Option() // Some(42)
All Result Methods
| Method | Description |
|---|---|
IsOk() / IsErr() |
State checks |
IsOkAnd(pred) / IsErrAnd(pred) |
State combined with a predicate |
Ok() / Err() |
Returns value / error (zero if the other state) |
Unwrap() / Expect(msg) |
Returns value, panics if Err |
UnwrapErr() |
Returns error, panics if Ok |
UnwrapOr(default) / UnwrapOrDefault() |
Returns value or fallback |
Map(fn) |
Transforms the value, may change its type |
MapOr(def, fn) / MapOrElse(defFn, fn) |
Map or fall back |
Then(fn) / ThenOf(fn) |
Chains Result / (T, error) functions |
Or(other) / OrElse(fn) |
Fallback Result |
MapErr(fn) / Wrap(err) |
Transforms / wraps the error |
Inspect(fn) / InspectErr(fn) |
Side effects, return self |
ErrIs(target) / ErrAs(target) |
errors.Is / errors.As integration |
Option() / Result() |
Converts to Option / (value, error) |
JSON: Ok(v) ⇄ {"ok": v}, Err(e) ⇄ {"err": "msg"} (externally tagged, like serde; duplicate keys rejected).
Extended slice with 90+ methods.
s := g.SliceOf(1, 2, 3, 4, 5)
s := g.NewSlice[int](0, 10) // empty, with capacity 10 (one arg sets len AND cap)
s.Len() // Int(5)
s.Get(0) // Some(1)
s.Get(100) // None (safe!)
s.Last() // Some(5)
s.Contains(3) // true
s.Push(6, 7) // append in place
s.Pop() // Some(7)
s.Clone() // safe copy
// Sorting
s.SortBy(cmp.Cmp) // ascending
s.Reverse() // in place
s.Shuffle() // random order
All Slice Methods
| Category | Methods |
|---|---|
| Access | Get, Last, First, Random, RandomSample |
| Modify | Set, Push, Pop, Insert, Remove, Clear |
| Search | Contains, ContainsBy, Index, IndexBy |
| Transform | Clone, Reverse, Shuffle, SortBy, SubSlice |
| Convert | Iter, Heap, Std |
| Info | Len, Cap, IsEmpty |
Extended map with Entry API.
m := g.NewMap[string, int]()
// or from pairs: g.MapOf(g.Pair[string, int]{"a", 1}, g.Pair[string, int]{"b", 2})
m.Insert("a", 1) // insert value
m.Get("a") // Some(1)
m.Get("x") // None
m.Contains("a") // true
m.Remove("a") // remove
m.Keys() // Slice of keys
m.Values() // Slice of values
Efficient update without multiple lookups:
// Insert if absent
m.Entry("counter").OrInsert(0)
// Update if present, insert if absent
m.Entry("counter").AndModify(func(v *int) { *v++ }).OrInsert(1)
// Lazy initialization
m.Entry("key").OrInsertWith(func() int {
return expensiveComputation()
})
Word frequency example:
words := g.SliceOf("apple", "banana", "apple", "cherry", "banana", "apple")
freq := g.NewMap[string, int]()
for _, word := range words {
freq.Entry(word).AndModify(func(v *int) { *v++ }).OrInsert(1)
}
// {"apple": 3, "banana": 2, "cherry": 1}
Entry Pattern Matching
switch e := m.Entry("key").(type) {
case g.OccupiedEntry[string, int]:
fmt.Println("Exists:", e.Get())
e.Insert(newValue) // replace
e.Remove() // delete
case g.VacantEntry[string, int]:
e.Insert(defaultValue) // insert
}
Collection of unique elements.
s := g.SetOf(1, 2, 3)
s.Insert(4) // add
s.Remove(1) // remove
s.Contains(2) // true
// Set algebra (lazy sequences)
s.Union(other) // A ∪ B
s.Intersection(other) // A ∩ B
s.Difference(other) // A \ B
s.SymmetricDifference(other) // A △ B
s.Disjoint(other) // no common elements?
a := g.SetOf(1, 2, 3, 4)
b := g.SetOf(3, 4, 5, 6)
a.Union(b).Collect() // {1, 2, 3, 4, 5, 6}
a.Intersection(b).Collect() // {3, 4}
a.Difference(b).Collect() // {1, 2}
a.SymmetricDifference(b).Collect() // {1, 2, 5, 6}
a.Subset(b) // false
a.Superset(b) // false
Priority queue (binary heap).
import "github.com/enetx/g/cmp"
// Min-heap (smallest first)
h := g.NewHeap(cmp.Cmp[int])
h.Push(5, 3, 8, 1, 9)
// or in one go: g.HeapOf(cmp.Cmp[int], 5, 3, 8, 1, 9)
h.Peek() // Some(1)
h.Pop() // Some(1)
h.Pop() // Some(3)
// Max-heap (largest first)
maxH := g.NewHeap(func(a, b int) cmp.Ordering {
return cmp.Cmp(b, a)
})
// From slice
heap := g.SliceOf(5, 3, 8, 1).Heap(cmp.Cmp)
Double-ended queue (ring buffer). O(1) at both ends.
dq := g.NewDeque[int]()
dq := g.DequeOf(1, 2, 3)
dq.PushFront(0) // add to front
dq.PushBack(4) // add to back
dq.Front() // Some(0)
dq.Back() // Some(4)
dq.PopFront() // Some(0)
dq.PopBack() // Some(4)
dq.Get(1) // element at index
dq.Len() // length
dq.IsEmpty() // true/false
Functional operations on sequences.
g.SliceOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).
Iter().
Filter(func(x int) bool { return x%2 == 0 }).
Map(func(x int) int { return x * x }).
Take(3).
Collect()
// [4, 16, 36]
Thanks to generic methods (Go 1.27+), transformations can change the element type right in the chain — just like Rust iterators:
g.SliceOf(1, 2, 3).
Iter().
Map(func(x int) g.String { return g.Int(x).String() }). // SeqSlice[int] -> SeqSlice[g.String]
Collect().
Join(", ").
Println() // 1, 2, 3
// Fold into any accumulator type
words := g.SliceOf[g.String]("a", "bb", "ccc")
total := words.Iter().Fold(g.Int(0), func(acc g.Int, w g.String) g.Int { return acc + w.Len() })
// 6
// Typed Zip — pairs keep their real types
nums := g.SliceOf(1, 2, 3)
names := g.SliceOf("one", "two", "three")
nums.Iter().Zip(names.Iter()).ForEach(func(n int, s string) {
fmt.Println(n, s)
})
// Chain iterators
g.SliceOf(1, 2).Iter().Chain(g.SliceOf(3, 4).Iter()).Collect()
// [1, 2, 3, 4]
// Sum with Fold
g.SliceOf(1, 2, 3, 4, 5).Iter().Fold(0, func(acc, x int) int { return acc + x })
// 15
// Enumerate
g.SliceOf("a", "b", "c").Iter().Enumerate().ForEach(func(i g.Int, v string) {
fmt.Printf("%d: %s\n", i, v)
})
// Using f package predicates
import "github.com/enetx/g/f"
g.SliceOf(1, 2, 3, 4, 5).Iter().Filter(f.Ne(3)).Collect() // [1, 2, 4, 5]
g.SliceOf("", "a", "").Iter().Exclude(f.IsZero).Collect() // ["a"]
All Iterator Methods
| Transform | Slice | Combine | Aggregate | Search | Other |
|---|---|---|---|---|---|
Map |
Take |
Chain |
Collect |
Find |
ForEach |
Filter |
Skip |
Zip |
Fold |
Any |
Inspect |
Exclude |
StepBy |
Enumerate |
Reduce |
All |
Range |
FilterMap |
First |
Intersperse |
Count |
MaxBy |
Scan |
FlatMap |
Last |
Cycle |
CounterBy |
MinBy |
Combinations |
Flatten |
Nth |
Partition |
Permutations |
||
Dedup |
Chunks |
ChunkBy |
Context |
||
Unique |
Windows |
Chan |
|||
SortBy |
TakeWhile |
||||
SkipWhile |
Typed mutex — data bound to lock.
counter := g.NewMutex(0)
// Lock and modify
guard := counter.Lock()
guard.Set(guard.Get() + 1)
guard.Unlock()
// With defer (recommended)
func increment(c *g.Mutex[int]) {
guard := c.Lock()
defer guard.Unlock()
guard.Set(guard.Get() + 1)
}
// Direct pointer access
guard := counter.Lock()
defer guard.Unlock()
*guard.Deref() += 1
// Non-blocking
if opt := counter.TryLock(); opt.IsSome() {
guard := opt.Unwrap()
defer guard.Unlock()
// got the lock
}
Why typed mutex?
// Traditional — easy to forget locking
type Old struct {
mu sync.Mutex
data map[string]int // what protects this?
}
// Typed — impossible to access without lock
type New struct {
data *g.Mutex[g.Map[string, int]]
}
func (s *New) Get(key string) g.Option[int] {
guard := s.data.Lock()
defer guard.Unlock()
return guard.Deref().Get(key)
}
Multiple readers OR single writer.
config := g.NewRwLock(Config{Port: 8080})
// Read (concurrent)
func getPort(c *g.RwLock[Config]) int {
guard := c.Read()
defer guard.Unlock()
return guard.Get().Port
}
// Write (exclusive)
func setPort(c *g.RwLock[Config], port int) {
guard := c.Write()
defer guard.Unlock()
guard.Deref().Port = port
}
// Non-blocking
if opt := config.TryRead(); opt.IsSome() { ... }
if opt := config.TryWrite(); opt.IsSome() { ... }
| Use Case | Choose |
|---|---|
| Read-heavy (config, cache) | RwLock |
| Write-heavy or balanced | Mutex |
| Simple counters | Mutex |
Goroutine pool for parallel tasks.
```go import "github.com/enetx/g/pool"
p := pool.Newint.Limit(4)
for i := range 10 { p.Go(func() g.Result[int] { return g.Ok(i * 2) }) }
for result := range p.Wait() {