Positionless: Making Go Struct Initialization Safer

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:

  1. Find every usage of the struct across your entire codebase
  2. Fix each one manually - the compiler won't suggest the fix
  3. 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

🍺 Homebrew
brew install flaticols/apps/positionless
Go Gopher Go
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:

  1. Zero configuration - Works out of the box
  2. Flexible path configuration - Analyze specific directories
  3. Auto-fix capability - Can automatically fix issues
  4. 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!