golot/stack
A generic, thread-safe, and performance-oriented LIFO (Last-In-First-Out) stack implementation for Go.
Overview
golot/stack provides a robust and easy-to-use stack data structure that leverages Go's generics for full type safety. It is designed for high-concurrency environments and includes advanced features for memory and performance tuning, such as pre-allocation and automatic memory shrinking.
Features
- Type-Safe: Built with Go generics (
[T any]) to ensure compile-time type safety.
- Thread-Safe: All operations are safe for concurrent access, protected by
sync.RWMutex.
- Zero-Value Ready: A declared
var s Stack[T] is an empty, usable stack with default settings.
- Performance-Tuned: Includes
NewWithCapacity to pre-allocate memory and avoid reallocations in performance-critical paths.
- Memory-Efficient: Features a configurable, automatic memory shrinking strategy to release unused memory after usage peaks.
- Clean API: Offers a simple and intuitive interface (
Push, Pop, Peek, Size, IsEmpty).
Installation
go get codeberg.org/WIZARDELF/golot/stack
API and Usage
Interface
The stack implements the following interface:
type IStack[T any] interface {
Size() int
IsEmpty() bool
Peek() (T, error)
Push(element T)
Pop() (T, error)
}
Basic Usage
Creating and using a stack is straightforward.
import "codeberg.org/WIZARDELF/golot/stack"
// Create a new stack for integers
s := stack.New[int]()
// Push elements onto the stack
s.Push(10)
s.Push(20)
// Peek at the top element
top, err := s.Peek()
// top: 20, err: nil
// Pop the top element
val, err := s.Pop()
// val: 20, err: nil
fmt.Printf("Stack size: %d\n", s.Size()) // "Stack size: 1"
Zero Value
The zero value of a Stack is a valid, empty stack ready for use.
var s stack.Stack[string] // No initialization needed
s.Push("hello")
val, _ := s.Pop() // val: "hello"
fmt.Println(s.IsEmpty()) // true
If you know the approximate number of elements you'll store, you can pre-allocate memory with NewWithCapacity to improve performance by preventing reallocations.
// Create a stack with an initial capacity for 100 elements
s := stack.NewWithCapacity[MyStruct](100)
for i := 0; i < 100; i++ {
s.Push(MyStruct{...}) // These pushes will not trigger a memory reallocation
}
Advanced Configuration: Memory Shrinking
The stack automatically shrinks its underlying storage to conserve memory. This behavior can be tuned or disabled using the WithShrinkHeuristics functional option.
// Example 1: Create a stack with a more aggressive shrink policy
// Shrink when length is below 25% of capacity, and capacity is > 32.
s1 := stack.New[int](
stack.WithShrinkHeuristics[int](0.25, 32),
)
// Example 2: Disable automatic shrinking entirely
// This can be useful in scenarios where performance is critical and memory
// usage patterns are predictable.
s2 := stack.New[int](
stack.WithShrinkHeuristics[int](0, 0),
)
Concurrency
All methods are thread-safe. You can safely use a single stack instance across multiple goroutines.
s := stack.New[int]()
var wg sync.WaitGroup
// Producer goroutine
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
s.Push(i)
}
}()
// Consumer goroutine
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
// Note: In a real-world scenario, you'd handle the error
// and the possibility that the stack is empty.
s.Pop()
}
}()
wg.Wait()
fmt.Println("Done. Stack is empty:", s.IsEmpty())