Packages & Imports
A package is a directory, capitalisation is the only access modifier, and there are exactly two visibility scopes.
Section 01 of 31 3 min read
Every Go file begins with package name. All .go files in the same directory belong to one package, and the package name is the namespace callers use: short, lowercase, one word. There is no class wrapper: functions, types, variables and constants live directly at package level.
Package declaration and imports
// File: UserService.java
package com.mycompany.service;
import com.mycompany.model.User;
import java.util.Optional;
public class UserService {
// A class is required: no free functions
public Optional<User> getUser(String id) {
return Optional.empty();
}
}// File: internal/service/user.go
package service
import (
"errors"
"github.com/mycompany/app/internal/model"
)
var ErrNotFound = errors.New("user not found")
// No class needed: functions live at package level
func GetUser(id string) (*model.User, error) {
return nil, ErrNotFound
}- The import path is the module path plus the directory:
github.com/mycompany/app/internal/model. The last element is conventionally the package name, so callers writemodel.User. - Standard-library imports come first, then a blank line, then everything else.
gofmtandgoimportsmaintain this for you. - An unused import is a compile error, not a warning. So is an unused local variable.
- Import cycles are forbidden. If
aimportsb,bcannot importa. This keeps dependency graphs acyclic and builds fast, and it often forces a better package design.
Exported vs unexported: capitalisation is the access modifier
public class User {
public String id; // public
public String email; // public
private String password; // private
public static User newUser(String email) { ... }
private String hashPw(String pw) { ... }
}type User struct {
ID string // Uppercase: exported
Email string // Uppercase: exported
password string // lowercase: package-private
}
// Exported function
func NewUser(email string) User { ... }
// Unexported function
func hashPw(pw string) string { ... }internal/: visibility at the module level
Java 9 modules let you control which packages a module exports. Go has a lighter mechanism, enforced by the go command when it builds: a package under a directory named internal can only be imported by code rooted at the parent of that internal directory. Put everything that is not a deliberate public API under internal/ and outsiders cannot depend on it.
github.com/mycompany/app/
├── internal/
│ └── billing/ ← importable only from github.com/mycompany/app/...
├── cmd/api/main.go ← can import app/internal/billing
└── go.mod
github.com/someone/else ← compile error: use of internal package not allowedCapital letter: exported
internal/: importable from inside only
internal/ goes further: only code rooted at the parent of that internal directory can import it.Naming conventions
| Java | Go |
|---|---|
com.mycompany.handlers package | handler: short, lowercase, singular, no underscores |
UserServiceImpl | Name the concrete type after what it is: PostgresStore, not StoreImpl |
getUserId() | UserID(): no Get prefix on getters; acronyms stay uppercase |
StringUtils.isBlank() | strings.TrimSpace(): the package name is part of the name |
IReader / ReaderInterface | Reader: one-method interfaces end in -er |