Packages and Modules in Golang
Packages and modules are fundamental concepts in Go that help organize and manage code. A package is a collection of Go files that are compiled together, while a module is a collection of related Go packages that are versioned and distributed together.
Packages
Every Go program is made up of packages. A package is a way to group related code together. The main package is special - it defines a standalone executable program.
Creating Packages
Create a new directory for your package and add Go files to it. All files in the same directory must belong to the same package.
package mathutils
import "math"
// Add returns the sum of two numbers
func Add(a, b float64) float64 {
return a + b
}
// Multiply returns the product of two numbers
func Multiply(a, b float64) float64 {
return a * b
}
// Sqrt returns the square root of a number
func Sqrt(x float64) float64 {
return math.Sqrt(x)
}Using Packages
To use a package, import it at the top of your file.
package main
import (
"fmt"
"myproject/mathutils" // Import your custom package
)
func main() {
result1 := mathutils.Add(5, 3)
result2 := mathutils.Multiply(4, 2)
result3 := mathutils.Sqrt(16)
fmt.Printf("5 + 3 = %.0f\n", result1)
fmt.Printf("4 * 2 = %.0f\n", result2)
fmt.Printf("Square root of 16 = %.0f\n", result3)
}Package Visibility
In Go, identifiers (functions, variables, types) that start with a capital letter are exported and can be accessed from other packages. Identifiers starting with a lowercase letter are private to the package.
package geometry
import "math"
// Point represents a 2D point - exported type
type Point struct {
X, Y float64
}
// Area calculates the area of a circle - exported function
func Area(radius float64) float64 {
return math.Pi * radius * radius
}
// perimeter calculates the perimeter of a circle - private function
func perimeter(radius float64) float64 {
return 2 * math.Pi * radius
}
// Circle represents a circle - exported type
type Circle struct {
Center Point
Radius float64
}
// Area method for Circle
func (c Circle) Area() float64 {
return Area(c.Radius)
}Modules
Modules were introduced in Go 1.11 to manage dependencies and versioning. A module is a collection of Go packages stored in a file tree with a go.mod file at its root.
Creating a Module
Use go mod init to create a new module.
go mod init myprojectThis creates a go.mod file:
module myproject
go 1.21Adding Dependencies
When you import packages that aren’t part of the standard library, Go will automatically download them.
package main
import (
"fmt"
"github.com/google/uuid" // External dependency
)
func main() {
id := uuid.New()
fmt.Println("Generated UUID:", id)
}Run go mod tidy to download dependencies and update go.mod and go.sum files.
Module Structure
A typical Go module structure:
myproject/
├── go.mod
├── go.sum
├── main.go
├── mathutils/
│ └── mathutils.go
└── cmd/
└── server/
└── main.goModule Commands
Common go mod commands:
go mod init <module-path>- Initialize a new modulego mod tidy- Add missing dependencies and remove unused onesgo mod download- Download modules to local cachego mod verify- Verify dependencies have expected contentgo mod why <module>- Explain why a module is neededgo mod graph- Print module requirement graph
Versioning
Modules use semantic versioning. You can specify versions in go.mod:
module myproject
go 1.21
require (
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
)Replacing Modules
You can replace a module with a local version for development:
module myproject
go 1.21
require github.com/example/mylib v1.0.0
replace github.com/example/mylib => ../mylibPackage Initialization
The init function is a special function that gets called automatically when a package is imported. It’s used for package-level initialization.
package database
import (
"database/sql"
"fmt"
_ "github.com/lib/pq" // Import for side effects
)
var db *sql.DB
func init() {
var err error
db, err = sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
if err != nil {
panic(fmt.Sprintf("Failed to connect to database: %v", err))
}
fmt.Println("Database connection initialized")
}
func GetDB() *sql.DB {
return db
}Blank Imports
Sometimes you need to import a package just for its side effects (like registering drivers). Use a blank import:
package main
import (
"database/sql"
_ "github.com/lib/pq" // Blank import - only for side effects
)
func main() {
db, err := sql.Open("postgres", "connection-string")
// Use db...
}Internal Packages
Use the internal directory to create packages that can only be imported by parent directories.
myproject/
├── internal/
│ ├── auth/
│ │ └── auth.go
│ └── utils/
│ └── helpers.go
├── cmd/
│ └── main.goThe internal/auth package can be imported by cmd/main.go but not by external modules.
Vendoring
Vendoring allows you to store dependencies locally. Run go mod vendor to create a vendor directory.
go mod vendorThen build with:
go build -mod=vendorBest Practices
- Use descriptive package names - The package name should reflect its purpose
- Keep packages focused - Each package should have a single responsibility
- Use internal for private APIs - Keep implementation details private
- Version your modules - Use semantic versioning for releases
- Document exported APIs - Use comments for exported functions and types
- Avoid circular dependencies - Design your package structure to prevent import cycles
Example: Complete Module Structure
Let’s create a complete example with proper module structure.
mkdir calculator
cd calculator
go mod init calculatormodule calculator
go 1.21package main
import (
"fmt"
"calculator/internal/operations"
)
func main() {
result := operations.Add(10, 5)
fmt.Printf("10 + 5 = %d\n", result)
result = operations.Multiply(10, 5)
fmt.Printf("10 * 5 = %d\n", result)
}package operations
// Add returns the sum of two integers
func Add(a, b int) int {
return a + b
}package operations
// Multiply returns the product of two integers
func Multiply(a, b int) int {
return a + b // Bug for demonstration
}package operations
import "testing"
func TestMultiply(t *testing.T) {
result := Multiply(3, 4)
expected := 12
if result != expected {
t.Errorf("Multiply(3, 4) = %d; want %d", result, expected)
}
}Common Patterns
Factory Pattern
package factory
import "fmt"
// Product interface
type Product interface {
Use()
}
// ConcreteProductA
type ConcreteProductA struct{}
func (p *ConcreteProductA) Use() {
fmt.Println("Using Product A")
}
// ConcreteProductB
type ConcreteProductB struct{}
func (p *ConcreteProductB) Use() {
fmt.Println("Using Product B")
}
// Factory function
func CreateProduct(productType string) Product {
switch productType {
case "A":
return &ConcreteProductA{}
case "B":
return &ConcreteProductB{}
default:
return nil
}
}Singleton Pattern
package singleton
import "sync"
type Singleton struct {
value int
}
var instance *Singleton
var once sync.Once
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{value: 42}
})
return instance
}
func (s *Singleton) GetValue() int {
return s.value
}External Resources:
Related Tutorials: