Agents 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.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/lang-go-library-dev" ~/.claude/skills/arustydev-agents-lang-go-library-dev && rm -rf "$T"
content/skills/lang-go-library-dev/SKILL.mdGo 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
- Foundational library patterns (API design, versioning, testing strategies)meta-library-dev
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 | |
| Add dependency | |
| Update dependencies | |
| Tidy dependencies | |
| Run tests | |
| Run tests with coverage | |
| Generate docs | |
| Format code | |
| Vet code | |
| 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
,jsonxml - No underscores or mixedCaps:
notioutilio_util - Descriptive:
notencoding/jsonencoding/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 | not |
| Use complete sentences | |
| Document parameters | |
| Document return values | |
| Include examples | Code blocks in comments |
| Mark deprecated items | |
Versioning and Releases
Semantic Versioning
Go modules use semantic versioning:
- Initial development, no stability guaranteesv0.x.x
- Stable API, backward compatible changes onlyv1.x.x
- Breaking changes require new major versionv2.x.x+
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
- Foundational library patternsmeta-library-dev
- Basic Go syntax and patternslang-go-dev- Go Modules Reference
- Effective Go
- Go API Guidelines
- pkg.go.dev - Package documentation