lang-go-library-dev
Go-specific library/package development patterns. Use when creating Go libraries, designing public APIs with Go idioms, configuring go.mod, managing module versioning, publishing packages, or writing package documentation. Extends meta-library-dev with Go tooling and ecosystem practices.
When & Why to Use This Skill
This Claude skill provides expert patterns and best practices for Go library and package development. It guides developers through designing idiomatic public APIs, managing Go modules (go.mod), implementing semantic versioning, and creating robust documentation and testing suites tailored specifically for the Go ecosystem.
Use Cases
- Designing idiomatic and flexible public APIs using the Functional Options pattern and minimal interface definitions.
- Configuring and managing Go modules, including handling major version upgrades (v2+) and dependency optimization via go.mod.
- Structuring complex Go projects using internal packages to encapsulate implementation details and prevent external coupling.
- Developing comprehensive test suites using table-driven tests, subtests, and runnable examples for automatic godoc generation.
- Preparing and publishing Go libraries to the ecosystem (pkg.go.dev) with proper semantic versioning, licensing, and documentation.
| name | lang-go-library-dev |
|---|---|
| description | Go-specific library/package development patterns. Use when creating Go libraries, designing public APIs with Go idioms, configuring go.mod, managing module versioning, publishing packages, or writing package documentation. Extends meta-library-dev with Go tooling and ecosystem practices. |
Go Library Development
Go-specific patterns for library/package development. This skill extends meta-library-dev with Go tooling, module management, and ecosystem practices.
This Skill Extends
meta-library-dev- Foundational library patterns (API design, versioning, testing strategies)
For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.
This Skill Adds
- Go tooling: go.mod, module versioning, workspaces, package documentation
- Go idioms: Interface design, error handling, API patterns, zero values
- Go ecosystem: pkg.go.dev, module proxies, major version strategies
This Skill Does NOT Cover
- General library patterns - see
meta-library-dev - Basic Go syntax - see
lang-go-dev - CLI development - see
lang-go-cli-dev - Web services - see
lang-go-http-dev
Quick Reference
| Task | Command/Pattern |
|---|---|
| New module | go mod init github.com/user/repo |
| Add dependency | go get package@version |
| Update dependencies | go get -u ./... |
| Tidy dependencies | go mod tidy |
| Run tests | go test ./... |
| Run tests with coverage | go test -cover ./... |
| Generate docs | go doc -all |
| Format code | go fmt ./... |
| Vet code | go vet ./... |
| Build | go build ./... |
Module Structure
go.mod Configuration
module github.com/username/mylibrary
go 1.21 // Minimum Go version
require (
github.com/pkg/errors v0.9.1
golang.org/x/sync v0.5.0
)
require (
// Indirect dependencies
golang.org/x/sys v0.15.0 // indirect
)
Best Practices
Use semantic import versioning for v2+:
// v0 or v1
module github.com/user/lib
// v2+
module github.com/user/lib/v2
Specify minimum Go version:
// Enables use of features from that version
go 1.21
Organize indirect dependencies:
# Let Go manage indirect deps automatically
go mod tidy
Package Organization
Standard Library Structure
mylibrary/
├── go.mod
├── go.sum
├── README.md
├── LICENSE
├── doc.go # Package-level documentation
├── library.go # Main public API
├── types.go # Public types
├── options.go # Configuration/options
├── errors.go # Error types
├── internal/ # Internal implementation
│ └── helper.go
├── testdata/ # Test fixtures
│ └── sample.json
└── example_test.go # Example tests for godoc
Package Naming
Good package names:
- Short, lowercase, single word:
http,json,xml - No underscores or mixedCaps:
ioutilnotio_util - Descriptive:
encoding/jsonnotencoding/j
Avoid:
// Bad: Generic names
package util
package common
package helpers
// Good: Specific names
package httputil
package stringutil
package mathutil
Internal Packages
mylibrary/
├── library.go # Public API
└── internal/ # Cannot be imported by external packages
├── parser.go
└── validator.go
Use internal/ to prevent external dependencies on implementation details.
Public API Design
Interface Design
Keep interfaces small:
// Good: Single method interface
type Reader interface {
Read(p []byte) (n int, err error)
}
// Good: Composed from small interfaces
type ReadWriter interface {
Reader
Writer
}
// Avoid: Large interfaces
type DataStore interface {
Read() error
Write() error
Delete() error
Update() error
// ... 10 more methods
}
Accept interfaces, return concrete types:
// Good: Accept interface (flexible)
func Process(r io.Reader) (*Result, error)
// Good: Return concrete type (clear contract)
func NewClient(url string) *Client
// Avoid: Returning interface unnecessarily
func NewClient(url string) ClientInterface
Define interfaces at point of use:
// Good: Define interface where you use it
package consumer
type DataFetcher interface {
FetchData(id string) ([]byte, error)
}
func ProcessData(fetcher DataFetcher) error {
// Use fetcher
}
// Not: Define interface in provider package
Functional Options Pattern
Preferred pattern for optional configuration:
type Client struct {
baseURL string
timeout time.Duration
maxRetries int
}
type Option func(*Client)
func WithTimeout(d time.Duration) Option {
return func(c *Client) {
c.timeout = d
}
}
func WithMaxRetries(n int) Option {
return func(c *Client) {
c.maxRetries = n
}
}
func NewClient(baseURL string, opts ...Option) *Client {
c := &Client{
baseURL: baseURL,
timeout: 30 * time.Second, // defaults
maxRetries: 3,
}
for _, opt := range opts {
opt(c)
}
return c
}
// Usage
client := NewClient("https://api.example.com",
WithTimeout(10*time.Second),
WithMaxRetries(5),
)
Constructor Patterns
New prefix for constructors:
// Returns pointer (can fail, stateful)
func NewClient(url string) (*Client, error) {
if url == "" {
return nil, errors.New("url required")
}
return &Client{url: url}, nil
}
// Must prefix for constructors that panic
func MustCompile(pattern string) *Regexp {
re, err := Compile(pattern)
if err != nil {
panic(err)
}
return re
}
Error Design
Create specific error types:
// Error type
type ValidationError struct {
Field string
Err error
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %v", e.Field, e.Err)
}
func (e *ValidationError) Unwrap() error {
return e.Err
}
// Sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
)
// Check with errors.Is
if errors.Is(err, ErrNotFound) {
// Handle not found
}
// Check type with errors.As
var validErr *ValidationError
if errors.As(err, &validErr) {
fmt.Println("Field:", validErr.Field)
}
Error wrapping:
// Wrap errors with context
if err != nil {
return fmt.Errorf("failed to connect to %s: %w", url, err)
}
// Allows callers to unwrap
originalErr := errors.Unwrap(err)
Zero Values
Design types to have useful zero values:
// Good: Zero value is useful
type Buffer struct {
buf []byte
}
var b Buffer
b.Write([]byte("hello")) // Works without initialization
// Good: Document when zero value is not useful
type Client struct {
baseURL string // Must be set
}
func NewClient(baseURL string) *Client {
return &Client{baseURL: baseURL}
}
Testing Patterns
Table-Driven Tests
Standard Go testing pattern:
func TestParse(t *testing.T) {
tests := []struct {
name string
input string
want Result
wantErr bool
}{
{
name: "valid input",
input: "key=value",
want: Result{Key: "key", Value: "value"},
},
{
name: "empty input",
input: "",
wantErr: true,
},
{
name: "multiple pairs",
input: "a=1,b=2",
want: Result{Pairs: map[string]string{"a": "1", "b": "2"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Parse(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
t.Errorf("Parse() = %v, want %v", got, tt.want)
}
})
}
}
Subtests
Organize tests hierarchically:
func TestClient(t *testing.T) {
t.Run("GET", func(t *testing.T) {
t.Run("success", func(t *testing.T) {
// Test successful GET
})
t.Run("not found", func(t *testing.T) {
// Test 404
})
})
t.Run("POST", func(t *testing.T) {
t.Run("success", func(t *testing.T) {
// Test successful POST
})
})
}
Test Helpers
Use t.Helper() to improve error messages:
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
Example Tests
Generate documentation with runnable examples:
// Example tests appear in godoc
func ExampleParse() {
result, _ := Parse("key=value")
fmt.Println(result.Key)
// Output: key
}
func ExampleParse_multiple() {
result, _ := Parse("a=1,b=2")
fmt.Println(len(result.Pairs))
// Output: 2
}
func ExampleClient_Get() {
client := NewClient("https://api.example.com")
data, _ := client.Get("/endpoint")
fmt.Printf("Got %d bytes\n", len(data))
// Output: Got 42 bytes
}
Test Coverage
# Run tests with coverage
go test -cover ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# View coverage by function
go tool cover -func=coverage.out
Documentation with godoc
Package Documentation
doc.go pattern:
// Package mylibrary provides utilities for parsing configuration files.
//
// This package supports JSON, YAML, and TOML formats with automatic
// format detection based on file extension.
//
// Basic usage:
//
// config, err := mylibrary.Load("config.yaml")
// if err != nil {
// log.Fatal(err)
// }
//
// The package provides three main types:
//
// - Config: The main configuration structure
// - Parser: Interface for custom parsers
// - Validator: Interface for custom validation
//
// For more examples, see the examples/ directory.
package mylibrary
Type and Function Documentation
Standard documentation format:
// Client represents an HTTP client for the API.
// It is safe for concurrent use by multiple goroutines.
type Client struct {
baseURL string
client *http.Client
}
// NewClient creates a new API client with the given base URL.
// It returns an error if the URL is invalid.
//
// Example:
//
// client, err := NewClient("https://api.example.com")
// if err != nil {
// log.Fatal(err)
// }
func NewClient(baseURL string) (*Client, error) {
// Implementation
}
// Get performs a GET request to the specified path.
// The path should not include the base URL.
//
// Get returns the response body as bytes. If the request fails
// or returns a non-2xx status code, an error is returned.
func (c *Client) Get(path string) ([]byte, error) {
// Implementation
}
Documentation Best Practices
| Practice | Example |
|---|---|
| Start with type/function name | // Client represents... not // This is... |
| Use complete sentences | // NewClient creates a new client. |
| Document parameters | // path should be relative to base URL |
| Document return values | // Returns nil if not found |
| Include examples | Code blocks in comments |
| Mark deprecated items | // Deprecated: Use NewClient instead. |
Versioning and Releases
Semantic Versioning
Go modules use semantic versioning:
v0.x.x- Initial development, no stability guaranteesv1.x.x- Stable API, backward compatible changes onlyv2.x.x+- Breaking changes require new major version
Major Version Strategy
v2+ requires module path change:
// v1
module github.com/user/lib
// v2 - add /v2 suffix
module github.com/user/lib/v2
In code:
// Import v2
import "github.com/user/lib/v2"
// Both v1 and v2 can coexist
import (
libv1 "github.com/user/lib"
libv2 "github.com/user/lib/v2"
)
Tagging Releases
# Tag v1 release
git tag v1.0.0
git push origin v1.0.0
# Tag v2 release (after creating /v2 module path)
git tag v2.0.0
git push origin v2.0.0
# Pre-release
git tag v1.1.0-beta.1
git push origin v1.1.0-beta.1
Retract Versions
Retract broken versions in go.mod:
module github.com/user/lib
go 1.21
retract (
v1.0.0 // Published accidentally
v1.0.1 // Critical bug, use v1.0.2+
[v1.5.0, v1.7.0] // Range retraction
)
Publishing and Distribution
Making Your Module Discoverable
Requirements for pkg.go.dev:
- Public repository on GitHub, GitLab, or Bitbucket
- Valid go.mod with module path matching repo
- Version tags using semantic versioning
- LICENSE file (required)
- Documentation in godoc format
Trigger indexing:
# pkg.go.dev automatically indexes on first request
curl https://pkg.go.dev/github.com/user/lib@v1.0.0
LICENSE
Common licenses for Go libraries:
- MIT - Permissive, simple
- Apache 2.0 - Permissive, patent grant
- BSD 3-Clause - Permissive, attribution required
Include LICENSE file in repository root.
README Best Practices
Include in README.md:
# My Library
Brief description of what the library does.
## Installation
```bash
go get github.com/user/lib
Quick Start
package main
import "github.com/user/lib"
func main() {
// Example usage
}
Documentation
Full documentation available at pkg.go.dev.
License
MIT License - see LICENSE file.
### Go Module Proxy
**By default, Go uses proxy.golang.org:**
```bash
# Verify module is available
go list -m -versions github.com/user/lib
# Force direct access (bypass proxy)
GOPRIVATE=github.com/user/* go get github.com/user/lib
API Compatibility
Backward Compatibility Rules
Within major version v1:
OK (minor/patch):
- Add new functions, types, methods
- Add new fields to structs (if not used in comparisons)
- Add new error types
- Make unexported symbols exported
NOT OK (requires v2):
- Remove or rename exported symbols
- Change function signatures
- Change error types returned
- Remove struct fields
- Change exported variable types
Deprecation Pattern
// Deprecated: Use NewClient instead.
// OldClient will be removed in v2.
func OldClient(url string) *Client {
return NewClient(url)
}
// NewClient creates a new API client.
func NewClient(url string) *Client {
// Implementation
}
Common Patterns
Config Struct with Defaults
type Config struct {
Timeout time.Duration
MaxRetries int
Debug bool
}
// DefaultConfig returns a Config with sensible defaults.
func DefaultConfig() *Config {
return &Config{
Timeout: 30 * time.Second,
MaxRetries: 3,
Debug: false,
}
}
// Usage
config := DefaultConfig()
config.Debug = true
Graceful Initialization
type Manager struct {
once sync.Once
db *sql.DB
}
func (m *Manager) init() {
m.once.Do(func() {
m.db, _ = sql.Open("postgres", "connection_string")
})
}
func (m *Manager) Query(q string) (*Result, error) {
m.init() // Lazy initialization
return m.db.Query(q)
}
Context for Cancellation
// Accept context as first parameter
func (c *Client) Fetch(ctx context.Context, url string) (*Response, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Process response
return parseResponse(resp)
}
// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
response, err := client.Fetch(ctx, "https://api.example.com")
Anti-Patterns
1. init() Functions
// Avoid: Non-deterministic initialization
func init() {
db = connectDatabase() // What if this fails?
}
// Prefer: Explicit initialization
func New() (*Client, error) {
db, err := connectDatabase()
if err != nil {
return nil, err
}
return &Client{db: db}, nil
}
2. Global State
// Avoid: Global variables
var defaultClient *Client
func Get(url string) (*Response, error) {
return defaultClient.Get(url)
}
// Prefer: Explicit dependencies
type Client struct { }
func (c *Client) Get(url string) (*Response, error) {
// Implementation
}
3. Interface Pollution
// Avoid: Interfaces for every type
type UserRepository interface {
Save(u User) error
}
type userRepository struct {}
// Prefer: Concrete types unless abstraction needed
type UserRepository struct {}
func (r *UserRepository) Save(u User) error {
// Implementation
}
4. Returning Pointers to Slices/Maps
// Avoid: Unnecessary pointer
func GetUsers() *[]User {
users := []User{ /* ... */ }
return &users
}
// Prefer: Return slice directly (already a reference type)
func GetUsers() []User {
return []User{ /* ... */ }
}
Troubleshooting
Module Path Issues
Problem: go get fails with "module not found"
Solutions:
- Ensure module path matches repository URL
- Check that repository is public
- Verify version tag exists:
git tag v1.0.0 - Wait for pkg.go.dev to index (can take a few minutes)
Import Cycle
Problem: "import cycle not allowed"
Solutions:
- Extract shared code to a separate package
- Use interfaces to break dependency
- Restructure package organization
// Before: Cycle between user and auth packages
package user
import "myapp/auth"
package auth
import "myapp/user"
// After: Extract common types to separate package
package types
type User struct { }
package user
import "myapp/types"
package auth
import "myapp/types"
Breaking API Changes
Problem: Need to make breaking change in v1
Solutions:
- Add new function, deprecate old
- Create v2 module with /v2 suffix
- Use options pattern for extensibility
// Instead of changing signature
func Process(data string) error
// Add new function
func ProcessWithOptions(data string, opts Options) error
References
meta-library-dev- Foundational library patternslang-go-dev- Basic Go syntax and patterns- Go Modules Reference
- Effective Go
- Go API Guidelines
- pkg.go.dev - Package documentation