Skip to content

Go Init Functions: Package Initialization

Overview

Master Go's init function for package initialization and setup tasks. This comprehensive guide covers init function behavior, execution order, practical use cases, and best practices, building upon our understanding of packages and program structure.

Key Points

  • Init functions run automatically before main
  • Multiple init functions per package allowed
  • Execution order follows import dependencies
  • Perfect for one-time setup and configuration
  • Cannot be called directly or have parameters

Understanding Init Functions in Go ⚙

The init function is Go's mechanism for package-level initialization, providing a way to perform setup tasks that must occur before the package is used.

Init Function Lifecycle

graph TD
    A[Program Start] --> B[Import Packages]
    B --> C[Initialize Variables]
    C --> D[Execute init Functions]
    D --> E[Run main Function]

    D --> F[Package A init]
    D --> G[Package B init]
    D --> H[Main Package init]

    F --> I[Variable Declarations]
    F --> J[init Function 1]
    F --> K[init Function 2]

    style A fill:#999,stroke:#333,stroke-width:2px,color:#000
    style D fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style E fill:#f3e5f5,stroke:#4a148c,stroke-width:2px

Basic Init Function Patterns 🏗

Init functions provide automatic initialization without explicit calls, making them perfect for setup tasks.

Fundamental Init Usage

Basic Init Functions

basic_init.go
package main

import "fmt"

var globalVar string

// Init function runs before main
func init() {
    globalVar = "Initialized in init"
    fmt.Println("Init function executed")
}

func main() {
    fmt.Println("Main function started")
    fmt.Printf("Global variable: %s\n", globalVar)

    // Output:
    // Init function executed
    // Main function started
    // Global variable: Initialized in init
}
multiple_init.go
package main

import "fmt"

var counter int

// First init function
func init() {
    counter++
    fmt.Printf("Init 1: counter = %d\n", counter)
}

// Second init function
func init() {
    counter++
    fmt.Printf("Init 2: counter = %d\n", counter)
}

func main() {
    fmt.Printf("Main: counter = %d\n", counter)

    // Output:
    // Init 1: counter = 1
    // Init 2: counter = 2
    // Main: counter = 2
}
package_setup.go
package config

import (
    "fmt"
    "os"
)

var (
    DatabaseURL string
    APIKey      string
    Debug       bool
)

func init() {
    // Load configuration from environment
    DatabaseURL = os.Getenv("DATABASE_URL")
    if DatabaseURL == "" {
        DatabaseURL = "localhost:5432"
    }

    APIKey = os.Getenv("API_KEY")
    if APIKey == "" {
        panic("API_KEY environment variable required")
    }

    Debug = os.Getenv("DEBUG") == "true"

    fmt.Println("Configuration loaded successfully")
}

Init Function Execution Order 🔄

Understanding execution order is crucial for proper initialization dependencies.

Execution Order Patterns

Execution Order Examples

single_package_order.go
package main

import "fmt"

var a = initVar("a")
var b = initVar("b")

func initVar(name string) string {
    fmt.Printf("Initializing variable %s\n", name)
    return name
}

func init() {
    fmt.Println("First init function")
}

var c = initVar("c")

func init() {
    fmt.Println("Second init function")
}

func main() {
    fmt.Println("Main function")
    fmt.Printf("Variables: %s, %s, %s\n", a, b, c)

    // Output:
    // Initializing variable a
    // Initializing variable b
    // Initializing variable c
    // First init function
    // Second init function
    // Main function
    // Variables: a, b, c
}
package_a/init.go
package a

import "fmt"

func init() {
    fmt.Println("Package A init")
}

func GetMessage() string {
    return "Hello from package A"
}
package_b/init.go
package b

import (
    "fmt"
    "./package_a"
)

func init() {
    fmt.Println("Package B init")
    fmt.Println("Using:", a.GetMessage())
}
main.go
package main

import (
    "fmt"
    "./package_a"
    "./package_b"
)

func init() {
    fmt.Println("Main package init")
}

func main() {
    fmt.Println("Main function")

    // Output:
    // Package A init
    // Package B init
    // Using: Hello from package A
    // Main package init
    // Main function
}

Practical Init Use Cases 🔧

Init functions excel at setup tasks that need to happen once per package import.

Common Init Patterns

Practical Applications

database_init.go
package database

import (
    "database/sql"
    "fmt"
    "log"
    "os"

    _ "github.com/lib/pq"  // PostgreSQL driver
)

var DB *sql.DB

func init() {
    var err error

    // Get connection string from environment
    connStr := os.Getenv("DATABASE_URL")
    if connStr == "" {
        connStr = "postgres://user:password@localhost/dbname?sslmode=disable"
    }

    // Initialize database connection
    DB, err = sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    // Test connection
    if err = DB.Ping(); err != nil {
        log.Fatal("Database ping failed:", err)
    }

    fmt.Println("Database connection established")
}

func GetDB() *sql.DB {
    return DB
}
logger_init.go
package logger

import (
    "io"
    "log"
    "os"
)

var (
    InfoLogger    *log.Logger
    WarningLogger *log.Logger
    ErrorLogger   *log.Logger
)

func init() {
    // Create or open log file
    logFile, err := os.OpenFile("app.log", 
        os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("Failed to open log file:", err)
    }

    // Create multi-writer for console and file
    multiWriter := io.MultiWriter(os.Stdout, logFile)

    // Initialize loggers with different prefixes
    InfoLogger = log.New(multiWriter, "INFO: ", 
        log.Ldate|log.Ltime|log.Lshortfile)
    WarningLogger = log.New(multiWriter, "WARNING: ", 
        log.Ldate|log.Ltime|log.Lshortfile)
    ErrorLogger = log.New(multiWriter, "ERROR: ", 
        log.Ldate|log.Ltime|log.Lshortfile)
}
features_init.go
package features

import "fmt"

type Feature struct {
    Name    string
    Enabled bool
}

var registry = make(map[string]*Feature)

func init() {
    // Register default features
    registerFeature("new_ui", false)
    registerFeature("beta_api", false)
    registerFeature("advanced_search", true)
    registerFeature("dark_mode", true)

    fmt.Printf("Registered %d features\n", len(registry))
}

func registerFeature(name string, enabled bool) {
    registry[name] = &Feature{
        Name:    name,
        Enabled: enabled,
    }
}

func IsEnabled(name string) bool {
    if feature, exists := registry[name]; exists {
        return feature.Enabled
    }
    return false
}

Best Practices and Performance 📓

Init Function Best Practices

  1. Keep Init Functions Simple

    // Good: Simple initialization
    func init() {
        config.Load()
        logger.Setup()
    }
    
    // Avoid: Complex logic in init
    func init() {
        // Avoid heavy computation or complex business logic
    }
    

  2. Handle Errors Appropriately

    1
    2
    3
    4
    5
    func init() {
        if err := setupCriticalResource(); err != nil {
            log.Fatal("Critical initialization failed:", err)
        }
    }
    

  3. Use Init for Package-Level Setup Only

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Good: Package-level configuration
    func init() {
        http.DefaultClient.Timeout = 30 * time.Second
    }
    
    // Avoid: Application-specific logic
    func init() {
        // Don't put main application logic here
    }
    

Common Pitfalls

  • Order Dependencies: Don't rely on init order between packages
  • Heavy Operations: Avoid time-consuming operations in init
  • Global State: Be careful with global variable initialization
  • Testing Issues: Init functions run during tests too

Quick Reference 📑

Key Takeaways

  1. Automatic Execution: Init runs before main, no explicit calls
  2. Multiple Functions: Can have multiple init functions per package
  3. Execution Order: Variables first, then init functions, then main
  4. Use Cases: Configuration, connections, registrations, setup
  5. Best Practices: Keep simple, handle errors, package-level only
  6. Testing: Remember init functions run during tests

Remember

"Init functions are Go's way of ensuring proper package initialization. Use them for essential setup tasks that must happen before your package is used. Keep them simple, handle errors gracefully, and remember they run automatically - no calls required!"