package main in Go: Understanding the Entry Point

package main in Go: Understanding the Entry Point

Go organizes code into packages. A package is a collection of source files in the same directory that share a package declaration. Most packages are libraries—they export functions and types for other code to use.

package main is different. It tells the Go compiler to produce an executable binary instead of a package archive.

package main

import "fmt"

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

When you run go build on this file, you get an executable. Change package main to package hello, and go build produces nothing—the compiler expects a main package for executables.

The main Function

Inside package main, you need a main function. This is the entry point:

func main() {
    // Program starts here
}

The main function takes no arguments and returns nothing. If you need command-line arguments, use os.Args. If you need to signal an error exit, use os.Exit(1).

The program terminates when main returns or when something calls os.Exit or log.Fatal.

Why package main Is Special

The Go toolchain has special behavior for package main:

  • go build produces an executable named after the directory or specified with -o
  • go run compiles and runs the program in one step
  • go install places the executable in $GOPATH/bin or $GOBIN

For other packages, go build compiles them but doesn't produce output unless they're being built as dependencies. go install places the compiled package in the build cache for linking.

One main Package Per Binary

A Go project can have multiple main packages, but each must be in a separate directory. Each main package becomes a separate executable.

Common structure for a project with multiple binaries:

project/
  cmd/
    server/
      main.go       (package main)
    client/
      main.go       (package main)
  internal/
    auth/
      auth.go       (package auth)

You'd build them separately:

go build ./cmd/server
go build ./cmd/client

This produces two executables: server and client.

Importing package main

You can't import package main. If you try, the compiler rejects it:

import "myproject/cmd/server"  // Error: can't import main package

This is intentional. package main is for executables, not libraries. If code needs to be shared, move it to a separate package that both main and other packages can import.

Testing package main

Go's testing framework works with package main. You can write tests for functions in main:

// main.go
package main

func add(a, b int) int {
    return a + b
}

func main() {
    result := add(2, 3)
    fmt.Println(result)
}

// main_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := add(2, 3)
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

Run tests with go test. The test file must also declare package main to access unexported functions.

The init Function

Packages can have init functions that run before main:

package main

import "fmt"

func init() {
    fmt.Println("Initialization")
}

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

Output:

Initialization
Main

init runs automatically when the package is initialized. You can have multiple init functions in the same package—they run in the order they appear in the source.

Use init for setup that must happen before main (registering drivers, initializing global state). Avoid complex logic in init—it makes programs harder to understand and test.

Command-Line Arguments

The main function doesn't take arguments, but os.Args provides access to them:

package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Println("Usage: program <name>")
        os.Exit(1)
    }
    fmt.Println("Hello,", os.Args[1])
}

os.Args[0] is the program name. Arguments start at index 1.

For more complex argument parsing, use the flag package:

package main

import (
    "flag"
    "fmt"
)

func main() {
    name := flag.String("name", "World", "name to greet")
    flag.Parse()
    fmt.Println("Hello,", *name)
}

Run with ./program -name=Alice.

Exit Codes

A successful program exits with code 0. For errors, use os.Exit:

if err != nil {
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1)
}

os.Exit terminates immediately without running deferred functions. If cleanup is needed, use return from main for normal exits and reserve os.Exit for error paths.

When Not to Use package main

If you're writing a library that other Go code will import, don't use package main. Use a descriptive package name instead:

package httputil

func BuildURL(base, path string) string {
    return base + "/" + path
}

Other code imports it:

import "myproject/httputil"

url := httputil.BuildURL("https://example.com", "api")

Libraries don't have a main function. They export functions and types for other packages to use.

Multiple Files in package main

A package main can span multiple files:

// main.go
package main

func main() {
    greet("World")
}

// greet.go
package main

import "fmt"

func greet(name string) {
    fmt.Println("Hello,", name)
}

As long as all files in the directory declare package main, they're part of the same package. Build with go build . and the compiler combines them into one executable.

Further Reading

The Go specification's section on program execution defines how package main and func main work.

Effective Go's chapter on package names explains naming conventions and when to use main vs library packages.

For structuring larger projects, see the Go project layout guide, which demonstrates how to organize cmd/, internal/, and pkg/ directories.

If package main is where your Go programs begin and you appreciate the simplicity of that entry point, our package main tee marks that starting point.

0 comments

Leave a comment

Please note, comments need to be approved before they are published.