Have you ever been bitten by a seemingly innocent struct field reordering? Or discovered that adding a new field to a struct broke code in unexpected places? If you've worked with Go for any length of time, you've probably encountered the fragility of positional struct literals. That's why I built positionless.
The Problem with Positional Struct Literals
Consider this innocent-looking code:
type Person struct {
Name string
Age int
Email string
}
// Somewhere in your codebase
person := Person{"John", 30, "[email protected]"}
Everything works fine until someone decides to reorder the fields:
type Person struct {
Name string
Email string // Moved up from third position
Age int // Moved down to third position
}
person := Person{"John", 30, "[email protected]"}
This produces these compilation errors:
./main.go:37:27: cannot use 30 (untyped int constant) as string value in struct literal
./main.go:37:31: cannot use "[email protected]" (untyped string constant) as int value in struct literal
The Go compiler catches the type mismatch, but this illustrates exactly why positional struct literals are dangerous. Even when the compiler catches it, you now have to:
- Find every usage of the struct across your entire codebase
- Fix each one manually - the compiler won't suggest the fix
- Risk missing some in large codebases
The real problem is maintenance burden and brittleness. When you have dozens of places initializing the same struct, any field changes require hunting down every single usage.
Real-World Use Cases
Large Codebase Migrations
When working with large codebases, positional struct literals become a maintenance nightmare. I've seen teams spend days fixing broken builds after routine struct changes.
// Common in large codebases - dozens of these scattered around
user := User{"alice", "[email protected]", time.Now(), true}
config := Config{8080, "localhost", "/tmp", 5, false}
request := Request{"GET", "/api/users", nil, map[string]string{"auth": "token"}}
After a simple field reordering or addition, you might face:
- 50+ compilation errors across multiple packages
- Manual fixes for each occurrence
- Risk of missing edge cases in complex initialization patterns
API Evolution and Backwards Compatibility
When evolving APIs, adding fields to structs can break existing code:
// Version 1: Simple config
type DatabaseConfig struct {
Host string
Port int
}
// Your users write:
config := DatabaseConfig{"localhost", 5432}
// Version 2: You add SSL support
type DatabaseConfig struct {
Host string
Port int
UseSSL bool // New field breaks existing positional usage
}
With positionless, your users' code becomes resilient to these changes.
Team Collaboration Benefits
Different team members might add fields in different orders, leading to merge conflicts and broken builds. Named initialization eliminates these issues entirely.
Why I Built Positionless
After encountering this issue multiple times in production code, I realized we needed a tool to systematically detect and fix these fragile patterns. Manual enforcement is error-prone and time-consuming.
Positionless provides:
- Automated detection across entire codebases
- One-click fixes with the
-fix
flag - Seamless integration with existing Go tooling
- CI/CD support to prevent regressions
Using Positionless in Your Project
Installation
brew install flaticols/apps/positionless
go install github.com/flaticols/positionless@latest
Basic Usage
# Analyze your entire project
positionless ./...
# Analyze a specific package
positionless ./internal/models
# Automatically fix issues
positionless -fix ./...
Integration with Go Vet
For seamless integration with your existing workflow:
go vet -vettool=$(which positionless) ./...
GitHub Actions Integration
Positionless is available as a native GitHub Action, making CI/CD integration incredibly simple. No need to manually install Go or the tool - just use the action directly:
name: Positionless Check
on:
pull_request:
types: [opened, synchronize]
jobs:
positionless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: flaticols/positionless@v2
with:
# Path to analyze (default: ./...)
path: ./...
# Apply fixes automatically
fix: true
# Include generated files (default: false)
include-generated: false
The action provides several powerful features:
- Zero configuration - Works out of the box
- Flexible path configuration - Analyze specific directories
- Auto-fix capability - Can automatically fix issues
- Control over generated files - Include or exclude as needed
Perfect Companion for fieldalignment
This is where positionless really shines!
If you're using fieldalignment
to optimize your struct memory layouts (and you should!), you've probably experienced the pain of broken code when fields get reordered. Positionless solves this problem completely.
The Problem with fieldalignment Alone
// Before fieldalignment - poorly aligned struct
type Config struct {
enabled bool // 1 byte
timeout int64 // 8 bytes (needs 7 bytes padding before it)
debug bool // 1 byte
maxRetries int32 // 4 bytes (needs 3 bytes padding before it)
} // Total: 32 bytes due to padding
// Your positional code works fine initially
config := Config{true, 5000, false, 3}
// After fieldalignment optimizes the layout:
type Config struct {
timeout int64 // 8 bytes (largest field first)
maxRetries int32 // 4 bytes
enabled bool // 1 byte
debug bool // 1 byte (+ 2 bytes padding at end)
} // Total: 16 bytes - 50% memory savings!
// Now your positional code breaks!
config := Config{true, 5000, false, 3}
This produces these compilation errors:
./main.go:15:18: cannot use true (untyped bool constant) as int64 value in struct literal
./main.go:15:24: cannot use 5000 (untyped int constant) as int64 value in struct literal
./main.go:15:30: cannot use false (untyped bool constant) as int64 value in struct literal
./main.go:15:37: cannot use 3 (untyped int constant) as int64 value in struct literal
The Solution: Use Both Tools Together
# STEP 1: First, make your code reordering-safe
positionless -fix ./...
# STEP 2: Now optimize memory layout without breaking anything
fieldalignment -fix ./...
After running both tools, your structs are:
- Memory-efficient (thanks to fieldalignment)
- Refactoring-safe (thanks to positionless)
- More maintainable with explicit field names
Try It Out
# Quick start
positionless ./...
The tool is open source and contributions are welcome. Check out the project on GitHub and let me know what you think!