Every Go program follows a consistent structure. Understanding this structure is the first step to mastering Go.
Every Go source file must start with a package declaration. This tells Go which package the file belongs to.
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Package types
package main: Used for executable programs. Must contain a main() function.
Named packages: Used for libraries and reusable code:
package calculator
package utils
package models
Package naming rules
mypackage, not my_package or MyPackage)The import statement brings in packages that your code needs to use. Go's import system supports aliases, grouping, and various organizational patterns that help keep code maintainable.
Single import
import "fmt"
Multiple imports
import (
"fmt"
"os"
"strings"
)
Import with alias
Go allows you to assign a custom name to an imported package using an alias. This feature addresses several practical needs that arise in real-world development.
The most common scenario where aliases become essential is when dealing with naming conflicts. Imagine you need to use both the standard library's context package and a third-party package like github.com/gorilla/context. Both would naturally be referred to as context, creating ambiguity. By assigning an alias to one of them—say, gcontext for the Gorilla package—you can use both without confusion.
Aliases also improve code readability when package names are verbose or don't clearly convey their purpose in your specific context. A long import path like github.com/company/very-long-package-name might be better served with a concise alias that better reflects how you're using it in your code.
During code migrations or refactoring, aliases provide a bridge for maintaining backward compatibility. When switching between package versions or restructuring dependencies, you can temporarily use aliases to keep existing code working while gradually updating references.
When you create an alias, Go establishes a new identifier that points to the imported package. This alias becomes the name you use throughout your code to access the package's exported identifiers. If you don't specify an alias, Go automatically uses the last component of the import path as the package name. For instance, importing "fmt" gives you access via fmt, while f "fmt" lets you use f instead.
It's important to note that each package can only be imported once per file, even with different aliases. You must choose a single import method per package. Here are the different alias syntax forms available:
// Default name (no alias)
import "fmt"
// Usage: fmt.Println()
// Custom alias
import f "fmt"
// Usage: f.Println()
// Dot import (imports package into current namespace)
import . "fmt"
// Usage: Println() (no package prefix needed)
// Blank import (for side effects only)
import _ "database/sql"
// Usage: Package is imported but not used directly
Here's a practical example demonstrating how aliases resolve naming conflicts:
import (
"context" // Standard library context
gcontext "github.com/gorilla/context" // Third-party context with alias
)
func handleRequest(ctx context.Context, req *http.Request) {
// Use standard library context
ctx = context.WithValue(ctx, "key", "value")
// Use Gorilla context with alias to avoid conflict
gcontext.Set(req, "session", "abc123")
}
Import groups
When your code requires multiple packages, Go provides a grouped import syntax using parentheses. This approach transforms what could be a cluttered list of individual import statements into a well-organized, readable structure that clearly communicates your code's dependencies.
The primary benefit of import groups lies in their ability to organize dependencies by source. By visually separating standard library packages from third-party dependencies and local project packages, you create a mental map that helps developers quickly understand where functionality originates. This organization becomes especially valuable as projects grow and the number of dependencies increases.
The Go community has established a convention that makes import groups predictable and maintainable. Imports are typically organized into three distinct groups: standard library packages first, followed by third-party packages, and finally local or project-specific packages. Within each group, imports are sorted alphabetically, which serves multiple purposes. Alphabetical ordering makes it easy to locate specific imports, prevents merge conflicts when multiple developers work on the same file, and creates a consistent structure that tools can automatically maintain.
Comments and blank lines play a crucial role in this organization. Comments clearly label each group, making the structure self-documenting. Blank lines between groups provide visual separation, improving readability at a glance. This combination of grouping, sorting, and visual separation transforms import statements from a necessary burden into a clear declaration of your code's dependencies.
Here's the standard format that has emerged as the Go community convention:
import (
// Standard library packages (sorted alphabetically)
"fmt"
"os"
"strings"
"time"
// Third-party packages (sorted alphabetically)
"github.com/gin-gonic/gin"
"github.com/lib/pq"
"golang.org/x/crypto/bcrypt"
// Local/project packages (sorted alphabetically)
"myproject/models"
"myproject/utils"
)
Fortunately, you don't need to manually maintain this organization. The goimports tool, an enhanced version of gofmt, automatically handles import management according to these conventions. When you save a file, most modern editors run goimports, which adds missing imports, removes unused ones, groups them by source, sorts them alphabetically, and adds appropriate comments and blank lines. This automation ensures consistent import organization across your entire codebase without requiring manual effort, allowing you to focus on writing code rather than formatting imports.
Special import forms
Dot import (. prefix): Allows you to use exported identifiers from the package without the package prefix. This is generally discouraged except in specific testing scenarios, as it can cause naming conflicts and reduce code clarity.
Blank import (_ prefix): Used when you need to import a package only for its side effects (like package initialization). The package is imported but not used directly in your code. Common examples include database drivers that register themselves during import.
import (
_ "github.com/lib/pq" // PostgreSQL driver registers itself
_ "github.com/go-sql-driver/mysql" // MySQL driver registers itself
)
Functions are the building blocks of Go programs. Here's the basic syntax:
func functionName(parameter1 type1, parameter2 type2) returnType {
// function body
return value
}
Basic function example
func greet(name string) string {
return "Hello, " + name
}
Multiple parameters
func add(a int, b int) int {
return a + b
}
// Shorthand when types are the same
func multiply(x, y int) int {
return x * y
}
No parameters
func getCurrentTime() time.Time {
return time.Now()
}
No return value
func printMessage(msg string) {
fmt.Println(msg)
}
Go supports two types of comments:
Single-line comments
// This is a single-line comment
var x int = 10 // Comment at end of line
Multi-line comments
/*
This is a multi-line comment.
It can span multiple lines.
Useful for longer explanations.
*/
Documentation comments
Go has special documentation comments that appear before exported declarations. These are used by go doc:
// Add calculates the sum of two integers.
// It returns the result of a + b.
func Add(a, b int) int {
return a + b
}
// Person represents a person with a name and age.
// This struct is used throughout the application.
type Person struct {
Name string // Name is the person's full name
Age int // Age is the person's age in years
}
Best practices
Viewing documentation
# View package documentation
go doc fmt
# View specific function
go doc fmt.Println
# Start documentation server
godoc -http=:6060