1. The Functional Options Pattern
Instead of passing a large config struct or multiple parameters, use functional options to make your API clean and extensible.
type Server struct {
host string
port int
timeout time.Duration
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(host string, port int, opts ...Option) *Server {
s := &Server{host: host, port: port, timeout: 30 * time.Second}
for _, opt := range opts { opt(s) }
return s
}
2. Slice Filtering without Allocation
You can filter a slice in place without allocating a new underlying array.
func Filter(s []int, keep func(int) bool) []int {
n := 0
for _, x := range s {
if keep(x) {
s[n] = x
n++
}
}
return s[:n]
}
3. WaitGroup Wrapper for Goroutines
A cleaner way to handle goroutines using a WaitGroup to prevent panic from leaving the WaitGroup unbalanced.
func RunSafe(wg *sync.WaitGroup, fn func()) {
wg.Add(1)
go func() {
defer wg.Done()
defer func() {
if err := recover(); err != nil {
log.Printf("Recovered from panic: %v", err)
}
}()
fn()
}()
}
4. Using Empty Structs for Sets
Since Go doesn't have a built-in Set type, you can use a map with empty structs to save memory, as `struct{}` takes 0 bytes.
type Set map[string]struct{}
func main() {
mySet := make(Set)
mySet["apple"] = struct{}{}
if _, exists := mySet["apple"]; exists {
fmt.Println("Apple is in the set!")
}
}