Zurück zum Blog

Das Go-Keyword package: package main, Namensregeln und Beispiele

Jede Go-Datei beginnt mit einer package-Klausel. Was package main macht, wie Package-Namen mit Verzeichnissen und Importpfaden zusammenhängen und welche Namensregeln gelten.

Das Go-Keyword package: package main, Namensregeln und Beispiele

Das Keyword package legt fest, zu welchem Package eine Go-Quelldatei gehört, und es muss die erste Codezeile jeder .go-Datei sein. package main ergibt ein ausführbares Programm. Jeder andere Name, etwa package billing, ergibt eine Library, die andere Packages importieren. Alle Dateien in einem Verzeichnis müssen denselben Package-Namen verwenden, und genau diesen Namen schreibt anderer Code vor den Punkt, um deine exportierten Namen zu erreichen, wie in billing.Total (Go spec).

Kurzfassung

  • Jede .go-Datei beginnt mit package name. Davor dürfen nur Kommentare und Leerzeilen stehen.
  • package main ist das Programm. Es braucht eine func main() ohne Argumente und ohne Rückgabewerte, und go build macht daraus ein Binary. Importieren lässt es sich nicht.
  • Ein Verzeichnis ist ein Package. Dateien mit unterschiedlichen Package-Namen im selben Ordner scheitern mit found packages billing (invoice.go) and payments (tax.go). Die wichtigste Ausnahme sind _test-Packages in Testdateien.
  • Package-Name und Importpfad sind zwei verschiedene Dinge. Du importierst "gopkg.in/yaml.v3" und rufst yaml.Unmarshal auf. Per Konvention entspricht der Name dem letzten Element des Pfads.
  • Gute Package-Namen sind kurze, kleingeschriebene Einzelwörter ohne Unterstriche oder mixedCaps. Vermeide util und common.
  • Sichtbarkeit wird pro Package entschieden. Ein großgeschriebener Name wird für andere Packages exportiert. Ein kleingeschriebener Name ist in jeder Datei desselben Packages sichtbar und sonst nirgends.

Was macht package main in Go?

package main sagt der Go-Toolchain, dass sie ein ausführbares Programm statt einer Library bauen soll. Die main-Funktion in diesem Package ist der Startpunkt des Programms, und wenn sie zurückkehrt, endet das Programm. Hier ist ein kompletter HTTP-Service in einer einzigen Datei:

example.gogo
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "ok")
	})
	log.Fatal(http.ListenAndServe(":8080", mux))
}

go run . kompiliert und startet ihn, und go build -o bin/api . schreibt ein Binary. Beide Regeln rund um main setzt die Toolchain durch. Ein package main ohne main-Funktion scheitert beim Linken:

example.texttext
runtime.main_main·f: function main is undeclared in the main package

Und wer ein Library-Package mit go run ./billing starten will, scheitert, bevor überhaupt etwas kompiliert wird:

example.texttext
package example.com/shop/billing is not a main package

Weil main das Programm ist, kann kein anderes Package es importieren. Wer es versucht, bekommt import "example.com/shop/cmd/api" is a program, not an importable package. Go-Projekte halten ihre Logik deshalb in Library-Packages und main klein. Ein Repository mit mehreren Binaries legt jedes in ein eigenes Verzeichnis, meist unter cmd/, und jedes dieser Verzeichnisse ist ein eigenes package main. Das Backend von LevelUpGo selbst hat 11 davon, vom API-Server bis zum Content-Importer, und alle teilen sich 22 Library-Packages unter internal/.

Können zwei Dateien in einem Verzeichnis unterschiedliche Package-Namen haben?

Nein. Das go-Kommando behandelt ein Verzeichnis als ein Package, also muss jede .go-Datei darin denselben Namen deklarieren. Angenommen, billing/invoice.go beginnt mit package billing, und jemand legt billing/tax.go mit package payments an. Dann bricht go build ./billing ab mit:

example.texttext
found packages billing (invoice.go) and payments (tax.go) in /home/dev/shop/billing

Die Lösung ist, eine der beiden Klauseln umzubenennen oder die Datei in ein eigenes Verzeichnis zu verschieben. Das Verzeichnis ist außerdem die kleinste Einheit, die du importieren kannst. Gehören zwei Dateien zu verschiedenen Packages, brauchen sie also verschiedene Ordner.

Ein Package auf mehrere Dateien aufzuteilen kostet dagegen nichts. Jede Datei im Verzeichnis sieht die Namen aller anderen Dateien, exportiert oder nicht, ohne etwas zu importieren. Ein billing-Package mit invoice.go, tax.go und refund.go verhält sich genau wie eine einzige lange Datei.

Was ist der Unterschied zwischen Package-Name und Importpfad?

Der Importpfad gibt an, wo das Package liegt, und der Package-Name ist der Bezeichner, den du im Code verwendest. Der Importpfad besteht aus dem Modulpfad aus go.mod plus dem Verzeichnis, etwa example.com/shop/billing. Der Package-Name kommt aus der package-Klausel der Dateien in diesem Verzeichnis.

Per Konvention stimmen beide überein, der Name ist also das letzte Element des Pfads. Die Tabelle zeigt einen Pfad, bei dem beide übereinstimmen, und die zwei üblichen Ausnahmen, die beide von Versionen kommen:

ImportpfadPackage-NameWarum sie sich unterscheiden
net/httphttpSie stimmen überein
gopkg.in/yaml.v3yamlDer Pfad trägt das Versionssuffix .v3
math/rand/v2randVerzeichnis für die Major-Version /v2
github.com/jackc/pgx/v5pgxVerzeichnis für die Major-Version /v5

Wenn zwei Imports denselben Namen haben, benennst du einen davon im Import-Block um. Code, der Session-Tokens erzeugt und außerdem Jitter für Retries einbaut, braucht oft sowohl crypto/rand als auch math/rand/v2, und beide heißen rand:

example.gogo
package tokens

import (
	"crypto/rand"
	"encoding/hex"
	mrand "math/rand/v2"
	"time"
)

// NewSessionID returns a random 32-character hex session ID.
func NewSessionID() string {
	b := make([]byte, 16)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// RetryDelay adds up to 100ms of jitter to a base delay.
func RetryDelay(base time.Duration) time.Duration {
	return base + time.Duration(mrand.IntN(100))*time.Millisecond
}

Die Umbenennung gilt nur in dieser Datei. Jede andere Datei sieht weiterhin den deklarierten Namen des Packages. Das Go-Keyword import behandelt Aliase, Blank-Imports und die anderen Import-Formen.

Ein Verzeichnis kann ein Package enthalten, dessen Name vom Ordnernamen abweicht, und Go kompiliert es trotzdem. Den Preis zahlen die Leser. Wer example.com/shop/httpapi-v2 importiert, muss erst in den Quellcode schauen, um zu erfahren, dass das Package httpapi heißt. Halte beide Namen gleich, solange kein Versionssuffix einen Unterschied erzwingt.

Wie benennt man ein Go-Package?

Für Go-Package-Namen gibt es eine kurze Liste von Regeln aus Effective Go und dem Artikel Package names im Go-Blog:

  • Kleingeschrieben, ein Wort: billing, httpapi, ratelimit. Nicht rate_limit oder rateLimit. Die Spezifikation erlaubt solche Namen, weil ein Package-Name ein beliebiger Bezeichner sein kann, aber idiomatischer Go-Code verwendet sie nicht.
  • Kurz und spezifisch. Der Name wird an jeder Aufrufstelle getippt. strconv ist besser als stringconversion, und auth ist besser als authenticationservice.
  • Benannt nach dem, was es bereitstellt. util, common, helpers, misc und models sagen nichts über den Inhalt aus, und sie wachsen zu Packages heran, die alles andere importiert. Teile sie stattdessen nach Zweck auf, etwa in money, slugify oder pagination.
  • Wiederhole den Package-Namen nicht in exportierten Namen, weil Aufrufer ihn ohnehin schreiben. http.Server ist richtig, http.HTTPServer nicht. billing.Total ist richtig, billing.BillingTotal nicht. Ein Konstruktor für den Haupttyp eines Packages heißt oft einfach New, wie bei ring.New.
  • Kein Keyword und nicht _. package type ist ein Syntaxfehler, und package _ scheitert mit invalid package name _.

Lies den Namen immer zusammen mit seinen exportierten Bezeichnern, denn so sieht ihn jeder Aufrufer. ratelimit.New(100, time.Minute) und config.Load("app.yaml") lesen sich wie Sätze. utils.NewRateLimiterUtil nicht.

Wie steuert package die Sichtbarkeit in Go?

Go hat keine Keywords wie public, private oder protected. Stattdessen ist das Package die Grenze. Ein Name, der mit einem Großbuchstaben beginnt, ist exportiert und für jedes Package sichtbar, das ihn importiert. Ein kleingeschriebener Name ist nicht exportiert und nur innerhalb seines eigenen Packages sichtbar, dort aber in allen Dateien.

example.gogo
// Package billing calculates invoice totals and applies VAT.
package billing

// VATPercent is the standard VAT rate applied to invoices.
const VATPercent = 25

// Total returns the sum of line items in cents, including VAT.
func Total(lineItems []int64) int64 {
	var sum int64
	for _, c := range lineItems {
		sum += c
	}
	return addVAT(sum)
}

func addVAT(cents int64) int64 {
	return cents + cents*VATPercent/100
}

Ein Handler in package main kann billing.Total aufrufen, aber billing.addVAT(1000) scheitert mit name addVAT not exported by package billing. Dieselbe Regel gilt für Struct-Felder und Methoden. Aus demselben Grund sieht encoding/json nur die exportierten Felder eines Structs.

Für Code, den mehrere deiner eigenen Packages gemeinsam nutzen, den externe Module aber nicht importieren sollen, gibt es in Go internal-Verzeichnisse. Ein Package unter example.com/shop/internal/pricing kann von allem importiert werden, was unter example.com/shop liegt, und von nichts anderem. Ein anderes Modul, das es trotzdem versucht, bekommt:

example.texttext
use of internal package example.com/shop/internal/pricing not allowed

Go-Projektstruktur erklärt, wann sich internal/ lohnt und wie du cmd/ und Library-Packages in einem echten Service anordnest.

Was ist ein _test-Package in Go?

Testdateien, also Dateien mit der Endung _test.go, können einen von zwei Package-Namen verwenden. package billing legt den Test in das Package selbst, er kann also nicht exportierte Funktionen aufrufen:

example.gogo
package billing

import "testing"

func TestAddVAT(t *testing.T) {
	if got := addVAT(1000); got != 1250 {
		t.Fatalf("addVAT(1000) = %d, want 1250", got)
	}
}

package billing_test macht daraus ein externes Test-Package. Es liegt im selben Verzeichnis, muss billing aber wie jeder andere Aufrufer importieren und kann nur exportierte Namen verwenden:

example.gogo
package billing_test

import (
	"testing"

	"example.com/shop/billing"
)

func TestTotal(t *testing.T) {
	got := billing.Total([]int64{1000, 2000})
	if got != 3750 {
		t.Fatalf("Total = %d, want 3750", got)
	}
}

Abgesehen von Dateien, die ein Build Constraint ausschließt, etwa ein Generator-Skript mit //go:build ignore, ist das Suffix _test die einzige Möglichkeit, dass sich zwei Package-Namen ein Verzeichnis teilen. go test baut die beiden Packages getrennt. Externe Tests prüfen die API so, wie Aufrufer sie verwenden, und auf diesem Weg schreibt die Standardbibliothek auch ausführbare Example-Funktionen, die in der Dokumentation erscheinen. Viele Go-Codebases mischen beide Arten, mit In-Package-Tests für knifflige Interna und _test-Packages für das öffentliche Verhalten.

Was darf vor der package-Klausel stehen?

Nur Kommentare und Leerzeilen. Zwei Arten von Kommentaren an dieser Stelle bedeuten der Toolchain etwas.

Ein Package-Doc-Kommentar steht direkt über package, ohne Leerzeile dazwischen. Per Konvention beginnt er mit Package billing, und go doc sowie pkg.go.dev zeigen ihn als Dokumentation des Packages an. Große Packages legen ihn oft in eine eigene Datei namens doc.go, die sonst nichts enthält. Bei einem Command mit package main beschreibt der Kommentar stattdessen das Programm, etwa // Api serves the shop's HTTP API.

Ein Build Constraint entscheidet, ob die Datei überhaupt kompiliert wird. Er muss vor der package-Klausel stehen, und danach muss eine Leerzeile folgen:

example.gogo
//go:build debug

package billing

import "log"

func init() {
	log.Println("billing: debug build")
}

Diese Datei wird nur mit go build -tags debug kompiliert. Dateien mit einem Betriebssystem- oder Architektur-Suffix im Namen, wie watch_linux.go oder asm_arm64.go, behandelt Go allein anhand des Dateinamens genauso, ganz ohne Kommentar.

Das Beispiel zeigt außerdem init. Ein Package kann beliebig viele init-Funktionen deklarieren, sogar mehrere in einer Datei. Go führt sie aus, nachdem die Variablen auf Package-Ebene initialisiert sind und bevor irgendetwas startet, das das Package importiert. Ein importiertes Package ist vollständig initialisiert, bevor das importierende Package an die Reihe kommt, daher läuft main immer zuletzt. Nutze init nur für einfaches Setup wie das Registrieren eines Treibers. Alles, was fehlschlagen kann, gehört in eine Funktion, die einen Fehler zurückgibt.

Wo LevelUpGo ins Spiel kommt

LevelUpGo bringt dir Go mit Übungen bei, die echten Go-Code im Browser ausführen. Der Kurs Packages & Organization behandelt exportierte Namen, init und Package-Zustand, Go-Module und Projektstruktur, und Code Organization zeigt, wie du eine wachsende Codebase in Packages mit klaren Namen aufteilst. Wenn du gerade erst anfängst, kommt Go Basics zuerst. Die anderen 24 reservierten Wörter findest du in Go Keywords: Alle 25 erklärt.

Häufig gestellte Fragen

Kann sich der Name eines Go-Packages vom Verzeichnisnamen unterscheiden?

Ja. Der Compiler liest nur die package-Klausel, daher lässt sich package httpapi in einem Ordner namens httpapi-v2 problemlos bauen. Die Konvention ist, dass der Name dem letzten Element des Importpfads entspricht. Die wichtigsten Ausnahmen sind Versionssuffixe wie gopkg.in/yaml.v3 (Package yaml) und math/rand/v2 (Package rand).

Warum braucht ein Go-Programm package main?

Das go-Kommando baut ein ausführbares Programm nur aus einem Package namens main, und die Runtime startet das Programm, indem sie die main-Funktion dieses Packages aufruft. Jedes andere Package ist eine Library. Ein package main ohne func main() scheitert mit function main is undeclared in the main package.

Kann ein Go-Modul mehr als ein package main haben?

Ja, eines pro Verzeichnis. Ein Service mit API-Server und Migrationstool hat meist cmd/api/main.go und cmd/migrate/main.go, die beide package main deklarieren. Du baust oder startest jedes über sein Verzeichnis: go run ./cmd/api oder go build ./cmd/migrate.

Darf ein Go-Package-Name Unterstriche oder Großbuchstaben enthalten?

Der Compiler erlaubt beides, weil ein Package-Name jeder gültige Bezeichner außer _ sein kann. Der Go-Stil rät davon ab. Der Artikel Package names im Go-Blog verlangt kurze, kleingeschriebene Namen „ohne under_scores oder mixedCaps“, wie strconv oder httputil.

Ist package ein Keyword in Go?

Ja. package ist eines der 25 reservierten Keywords von Go, du kannst es also nicht als Variablen- oder Funktionsnamen verwenden. Go-Code, der eine Variable für ein Package braucht, nennt sie meist pkg.

Quellen

Schreib Go wie ein Senior Engineer

Interaktive Lektionen im Browser. Die ersten sind kostenlos.

Kostenlose Lektion testenOder kostenloses Konto erstellen