Skip to main content

Package System — The Foundation of Go Code Organization

All Go code is organized in packages. A package is Go's basic code organization unit that groups related functionality together.

Package Declaration and Imports

package main // Package declaration — first line of every .go file

import (
"fmt" // Standard library
"math/rand" // Sub-package
"os"

"github.com/gin-gonic/gin" // External package (requires module)
)

func main() {
fmt.Println("Hello, Go!")
}

Package naming rules:

  • Lowercase only
  • Short and clear (ideally one word)
  • Should match directory name (exceptions: main, _test)
  • Avoid plural forms or common prefixes

Exported vs Unexported

Go determines visibility by the first letter's case— uppercase means exported (public), lowercase means unexported (private).

package geometry

import "math"

// ✅ Exported — accessible from other packages
type Circle struct {
Radius float64 // Exported field
color string // Unexported field (only accessible in same package)
}

// ✅ Exported function
func NewCircle(radius float64) *Circle {
return &Circle{
Radius: radius,
color: "red", // Unexported fields can only be set in same package
}
}

// ✅ Exported method
func (c *Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}

// ❌ Unexported method — only usable within same package
func (c *Circle) validate() bool {
return c.Radius > 0
}

// Exported constant
const Pi = 3.14159

// Unexported constant
const maxRadius = 1000.0

Usage example:

package main

import (
"fmt"
"myproject/geometry"
)

func main() {
c := geometry.NewCircle(5.0)
fmt.Println(c.Area()) // ✅ Can access exported method
fmt.Println(c.Radius) // ✅ Can access exported field
// fmt.Println(c.color) // ❌ Compile error
// c.validate() // ❌ Compile error
}

init Function — Package Initialization

The init() function runs automatically when a package is first loaded.

package database

import (
"fmt"
"log"
)

var (
connectionPool *Pool
maxConnections = 10
)

// init function: auto-executes when package loads
func init() {
fmt.Println("Initializing database package...")
var err error
connectionPool, err = newPool(maxConnections)
if err != nil {
log.Fatalf("Failed to initialize DB pool: %v", err)
}
fmt.Println("Database package initialized")
}

type Pool struct {
size int
}

func newPool(size int) (*Pool, error) {
return &Pool{size: size}, nil
}

init Function Characteristics

package mypackage

import "fmt"

var order []string

func init() {
order = append(order, "first init")
fmt.Println("init #1 executed")
}

func init() {
// Multiple init functions allowed in the same file!
order = append(order, "second init")
fmt.Println("init #2 executed")
}

// init takes no arguments and returns no values
// Cannot be called directly (compile error if you try)

init execution order:

  1. Package-level variable initialization
  2. init() functions in file order
  3. Imported packages' init runs first
package main

import (
_ "myproject/database" // Import for side effects only (runs init)
"fmt"
)

func main() {
fmt.Println("main executing")
// Output order:
// Initializing database package...
// Database package initialized
// main executing
}

Import Aliases and Blank Imports

package main

import (
"fmt"

// Alias import — resolves name conflicts
mrand "math/rand"
crand "crypto/rand"

// Blank import — only for running init
_ "github.com/lib/pq" // Registers PostgreSQL driver

// Dot import — use without package prefix (not recommended)
. "math"
)

func main() {
fmt.Println(mrand.Intn(100)) // Use math/rand
fmt.Println(crand.Reader) // Use crypto/rand
fmt.Println(Pi) // Use math.Pi directly as Pi
}

Practical Package Structure

A realistic package structure example.

myapp/
├── main.go # package main
├── cmd/
│ └── server/
│ └── main.go # package main (server entry point)
├── internal/ # Cannot be imported from external packages
│ ├── auth/
│ │ └── auth.go # package auth
│ └── db/
│ └── db.go # package db
├── pkg/ # Packages to expose externally
│ └── validator/
│ └── validator.go # package validator
└── api/
└── handlers.go # package api
// internal/auth/auth.go
package auth

import (
"crypto/rand"
"encoding/hex"
"errors"
)

// ErrInvalidToken — exported error (sentinel)
var ErrInvalidToken = errors.New("invalid token")

// Token — exported type
type Token struct {
Value string
UserID string
ExpiresAt int64
}

// Generate — exported function
func Generate(userID string) (*Token, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return &Token{
Value: hex.EncodeToString(b),
UserID: userID,
}, nil
}

// validate — unexported (internal only)
func validate(token string) bool {
return len(token) == 64
}

Package Documentation

Add documentation to Go packages by writing comments above the package declaration.

// Package geometry provides calculations for geometric shapes.
//
// It computes areas and perimeters for circles, triangles,
// rectangles, and other shapes.
//
// Example usage:
//
// c := geometry.NewCircle(5.0)
// fmt.Println(c.Area())
package geometry

Use go doc to view documentation:

go doc geometry
go doc geometry.Circle
go doc geometry.Circle.Area

Key Takeaways

  • Exported/Unexported: First letter uppercase = exported, lowercase = unexported
  • init function: Runs automatically on package load, cannot be called directly
  • _ blank import: Import for init or side effects only
  • Package name = directory name: Convention to keep them matching
  • internal package: Only importable within the same module