Das Keyword import macht die exportierten Namen eines anderen Packages in einer Go-Quelldatei verfügbar. Imports stehen direkt nach der package-Klausel und vor allen anderen Deklarationen. Jeder Import benennt ein Package über seinen Importpfad, etwa "net/http" oder "github.com/jackc/pgx/v5". Auf den Inhalt des Packages greifst du dann über seinen Namen zu, wie in http.ListenAndServe. Ein Import kann außerdem einen Alias, den Blank Identifier _ oder einen Punkt . tragen, und jede dieser Formen ändert, wie der Name des Packages in der Datei gebunden wird (Go spec).
Kurzfassung
- Imports stehen nach
packageund vor allem anderen. Die meisten Dateien verwenden einen einzigen gruppierten Blockimport ( ... ). - Ein Importpfad ist ein String. Pfade der Standardbibliothek sind kurz (
"encoding/json"), alle anderen beginnen mit einem Modulpfad ausgo.mod. - Ein ungenutzter Import ist ein Compilerfehler:
"os" imported and not used. goimports und gopls fügen Imports für dich hinzu und entfernen sie wieder. alias "path"benennt ein Package innerhalb einer Datei um. Das brauchst du, wenn zwei Imports denselben Namen haben, wiehtml/templateundtext/template._ "path"importiert ein Package nur wegen seiner Seiteneffekte, die in seineninit-Funktionen laufen. Datenbanktreiber undnet/http/pproffunktionieren so.. "path"holt Namen ohne Präfix in die Datei. Styleguides raten davon ab, und staticcheck meldet es als ST1001. Ausnahmen gibt es nur in seltenen Testfällen.- Go lehnt Import-Zyklen ab. Die Lösung ist ein neues gemeinsames Package oder ein Interface, das dort definiert wird, wo es gebraucht wird.
- Imports gelten pro Datei. Jede Datei eines Packages importiert, was sie verwendet, auch wenn eine Nachbardatei dasselbe Package schon importiert.
Wie importiert man ein Package in Go?
Schreib import und dahinter den Pfad des Packages in doppelten Anführungszeichen. Eine Datei, die nur ein Package braucht, kommt mit einer einzelnen Zeile aus. Eine Datei, die mehrere braucht, verwendet einen gruppierten Block mit Klammern:
example.gogopackage main import "fmt" func main() { fmt.Println("ok") }
example.gogopackage main import ( "encoding/json" "log" "net/http" ) type healthResponse struct { Status string `json:"status"` } func main() { http.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(healthResponse{Status: "ok"}) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
Beide Formen bedeuten dasselbe, und fast jeder Go-Code verwendet den gruppierten Block. Imports stehen immer nach der package-Klausel und vor jedem const, var, type oder func. Setzt du einen Import weiter nach unten, bricht der Parser mit syntax error: imports must appear before other declarations ab. Innerhalb einer Funktion kannst du ebenfalls nichts importieren.
Die Reihenfolge im Block übernehmen Formatierungstools für dich. gofmt sortiert die Zeilen innerhalb jeder Gruppe nach Importpfad. goimports, das die meisten Editoren beim Speichern ausführen, teilt den Block außerdem in zwei Gruppen mit einer Leerzeile dazwischen. Die Standardbibliothek kommt zuerst, alles andere danach:
example.gogoimport ( "context" "fmt" "net/http" "example.com/shop/billing" "github.com/jackc/pgx/v5" )
Auf das Programm hat die Reihenfolge keinen Einfluss. Sie hält Diffs klein, und Leser sehen auf einen Blick, welche Abhängigkeiten von außerhalb der Standardbibliothek kommen.
Was ist ein Importpfad in Go?
Ein Importpfad ist der String, der dem go-Kommando sagt, wo ein Package liegt. Packages der Standardbibliothek haben kurze Pfade ohne Punkt im ersten Element, etwa "fmt", "net/http" oder "crypto/rand". Der Pfad jedes anderen Packages beginnt mit einem Modulpfad.
Deine eigenen Packages verwenden den in go.mod deklarierten Modulpfad plus das Verzeichnis. Steht module example.com/shop in go.mod, importierst du das Package in ./billing als "example.com/shop/billing". Relative Imports wie "./billing" gibt es im Modulmodus nicht. Ein lokales Package importierst du also immer über seinen vollständigen Pfad.
Packages von Drittanbietern funktionieren genauso, und das go-Kommando löst sie über go.mod auf:
go get github.com/jackc/pgx/v5lädt das Modul herunter und fügt einerequire-Zeile ingo.modein.- Du schreibst
"github.com/jackc/pgx/v5"in den Import-Block. go mod tidyergänzt später jedes Modul, das deine Imports brauchen, und entfernt alle, die nichts mehr importiert.
Das letzte Element des Pfads ist meist der Package-Name, aber nicht immer. "github.com/jackc/pgx/v5" stellt ein Package namens pgx bereit, und "gopkg.in/yaml.v3" stellt yaml bereit. Das Go-Keyword package erklärt, wie Namen und Pfade zusammenhängen.
Warum kompiliert Go keine ungenutzten Imports?
Go behandelt einen ungenutzten Import als Fehler, nicht als Warnung. Lässt du "os" in einer Datei stehen, die es nicht mehr aufruft, bricht go build ab:
example.texttext./main.go:5:2: "os" imported and not used
Die Begründung steht in der Go FAQ. Ein ungenutzter Import verlangsamt das Kompilieren und fügt eine Abhängigkeit hinzu, die das Programm nicht braucht. In einer großen Codebase sammeln sich solche Imports an. Warnungen ignoriert man, deshalb macht Go daraus einen Fehler. go mod tidy räumt go.mod auf dieselbe Weise auf und entfernt Anforderungen, die nichts importiert.
In der Praxis behebst du solche Fehler selten von Hand. goimports und gopls, der Go-Language-Server hinter der Go-Unterstützung von VS Code und GoLand, löschen ungenutzte Imports und ergänzen fehlende, sobald du speicherst. Willst du beim Debuggen einen Import kurz behalten, weise etwas daraus dem Blank Identifier zu, wie in var _ = os.Exit. Entferne die Zeile, bevor du committest.
Wie benennt man einen Import in Go um?
Setz einen Alias vor den Pfad. Der Alias ersetzt den Package-Namen, aber nur in dieser Datei:
example.gogoimport htmltemplate "html/template"
Der übliche Grund ist ein Namenskonflikt. html/template und text/template heißen beide template, und ein E-Mail-Service braucht oft beide: reinen Text für die Betreffzeile und HTML-escapte Ausgabe für den Inhalt. Importierst du beide ohne Alias, scheitert der Build mit template redeclared in this block. Ein Alias für einen der beiden behebt das:
example.gogopackage main import ( htmltemplate "html/template" "os" "text/template" ) func main() { subject := template.Must(template.New("subject").Parse("Your invoice {{.ID}}\n")) body := htmltemplate.Must(htmltemplate.New("body").Parse("<p>{{.Note}}</p>\n")) data := map[string]string{"ID": "INV-42", "Note": "<script>alert(1)</script>"} subject.Execute(os.Stdout, data) body.Execute(os.Stdout, data) }
Die Ausgabe zeigt, warum es die beiden Packages getrennt gibt. Der Betreff lautet Your invoice INV-42. Den Inhalt escapt html/template zu <p><script>alert(1)</script></p>.
Aliase tauchen auch dort auf, wo viele Packages dasselbe allgemeine letzte Element haben. Kubernetes-Code importiert mehrere Packages, die alle v1 heißen, und schreibt deshalb corev1 "k8s.io/api/core/v1" und metav1 "k8s.io/apimachinery/pkg/apis/meta/v1". Gibt es keinen Konflikt, behältst du den echten Namen. http. erkennt jeder, bei einem eigenen Alias muss man dagegen erst im Import-Block nachsehen, wofür er steht.
Was macht ein Blank-Import _ in Go?
Ein Blank-Import lädt ein Package, ohne seinen Namen zu binden. Du kannst nichts daraus aufrufen, aber das Package wird trotzdem initialisiert. Seine Variablen auf Package-Ebene werden gesetzt, und seine init-Funktionen laufen. Manche Packages erledigen ihre eigentliche Arbeit in init, indem sie sich bei einem anderen Package registrieren.
Das häufigste Beispiel ist ein Treiber für database/sql:
example.gogopackage main import ( "database/sql" "log" "os" _ "github.com/jackc/pgx/v5/stdlib" ) func main() { db, err := sql.Open("pgx", os.Getenv("DATABASE_URL")) if err != nil { log.Fatal(err) } defer db.Close() }
Das Package stdlib von pgx ruft in seinem init die Funktion sql.Register("pgx", ...) auf. Dein Code spricht nur mit database/sql. Ohne den Blank-Import registriert sich der Treiber aber nie, und sql.Open gibt sql: unknown driver "pgx" (forgotten import?) zurück.
Weitere Blank-Imports, die dir in echten Services begegnen:
| Import | Was sein init macht |
|---|---|
_ "github.com/jackc/pgx/v5/stdlib" | Registriert den Treiber pgx bei database/sql |
_ "net/http/pprof" | Fügt Profiling-Handler unter /debug/pprof/ zu http.DefaultServeMux hinzu |
_ "image/png" | Registriert den PNG-Decoder, damit image.Decode PNG-Dateien lesen kann |
_ "time/tzdata" | Bettet die Zeitzonendatenbank ein, für Container, die keine haben |
_ "embed" | Zur Laufzeit nichts. Der Import ist Voraussetzung dafür, dass //go:embed auf einer string- oder []byte-Variable funktioniert |
Der Fall embed ist eine Regel der Toolchain und kein Seiteneffekt von init. Fehlt der Import, scheitert der Build mit go:embed requires import "embed" (or import _ "embed", if package is not used).
Die Reihenfolge der Initialisierung ist vorhersehbar. Jedes importierte Package wird vollständig initialisiert, samt seiner eigenen Imports, bevor das Package an die Reihe kommt, das es importiert. Seit Go 1.21 legt die Spezifikation auch die Reihenfolge zwischen Packages fest, die nichts miteinander zu tun haben. Sie werden in der Reihenfolge ihrer Importpfade initialisiert. Innerhalb eines Packages werden zuerst die Variablen auf Package-Ebene gesetzt. Danach laufen die init-Funktionen in der Reihenfolge, in der die Dateien dem Compiler übergeben werden, und das go-Kommando sortiert sie nach Dateiname. main läuft zuletzt. Ein Package, das von mehreren anderen importiert wird, wird trotzdem nur einmal initialisiert.
Blank-Imports gehören in main oder in das Package, das den Seiteneffekt tatsächlich braucht, nicht in eine Library, die andere importieren. Eine Library, die net/http/pprof per Blank-Import einbindet, öffnet Profiling-Endpunkte in jedem Programm, das sie verwendet, ob das Programm sie will oder nicht.
Solltest du Dot-Imports in Go verwenden?
Fast nie. Ein Dot-Import holt die exportierten Namen eines Packages direkt in die Datei, du rufst sie also ohne Package-Namen auf:
example.gogoimport . "strings" func normalizeEmail(email string) string { return ToLower(TrimSpace(email)) }
In einer dreizeiligen Funktion ist das leicht zu verfolgen. In einer langen Datei kann man bei ToLower aber nicht erkennen, ob die Funktion in diesem Package definiert ist oder aus welchem Import sie stammt. Die Seite Go Code Review Comments rät davon ab, und staticcheck meldet es:
example.texttextmain.go:3:8: should not use dot imports (ST1001)
Die einzige akzeptierte Verwendung ist ein Test, der wegen eines Zyklus außerhalb des getesteten Packages liegen muss. Code Review Comments nennt als Beispiel package foo_test, das bar/testutil importiert, das wiederum foo importiert. Mit einem Dot-Import von foo liest sich dieser Test so, als läge er im Package selbst. Einige Test-Frameworks wie Ginkgo und Gomega dokumentieren Dot-Imports außerdem für ihre Matcher. Außerhalb dieser Fälle schreibst du den Package-Namen aus.
Was ist ein Import-Zyklus in Go?
Ein Import-Zyklus entsteht, wenn sich zwei oder mehr Packages gegenseitig importieren, direkt oder über eine Kette. Go verbietet das. Importiert billing also customers, während customers billing importiert, scheitert der Build:
example.texttextpackage example.com/shop/billing imports example.com/shop/customers from billing.go imports example.com/shop/billing from customers.go: import cycle not allowed
Die Regel hält die Initialisierungsreihenfolge eindeutig und die Builds schnell, weil der Compiler ein Package immer nach allem kompilieren kann, wovon es abhängt. Außerdem sagt sie etwas über das Design aus: Ein Zyklus bedeutet meist, dass zwei Packages eigentlich eines sind oder dass beide von etwas abhängen, das in ein drittes gehört.
Es gibt zwei gängige Lösungen:
- Verschieb den gemeinsamen Teil in ein eigenes Package. Brauchen
billingundcustomersbeide einen TypCustomerID, leg ihn in ein kleines Packageshop/idsodershop/domain, das keines der beiden importiert. - Definier ein Interface dort, wo es gebraucht wird. Muss
billingnur die E-Mail-Adresse eines Kunden nachschlagen, kann es deklarieren, was es braucht, undmainübergibt die echte Implementierung:
example.gogopackage billing import "context" // CustomerLookup is the one thing billing needs from the customers package. type CustomerLookup interface { Email(ctx context.Context, customerID string) (string, error) } type Service struct { customers CustomerLookup } func NewService(customers CustomerLookup) *Service { return &Service{customers: customers} }
Jetzt importiert billing nicht mehr customers, und customers kann billing ohne Probleme importieren. Jeder Typ mit einer passenden Email-Methode erfüllt das Interface, ohne es zu nennen, und die Abhängigkeit zeigt nur noch in eine Richtung.
Eine verwandte Regel wendet das go-Kommando auf package main an. Kein Package kann es importieren, und wer es versucht, bekommt import "example.com/shop/cmd/api" is a program, not an importable package.
Gelten Go-Imports pro Datei oder pro Package?
Imports gelten pro Datei. Ein Import bindet den Package-Namen nur in der Datei, in der er steht, obwohl sich alle Dateien eines Packages die übrigen Namen auf Package-Ebene teilen. Hier importiert server.go das Package log:
example.gogopackage main import "log" func main() { log.Println("server starting") runJobs() }
Und jobs.go im selben Package ruft log auf, ohne es zu importieren:
example.gogopackage main func runJobs() { log.Println("running jobs") }
runJobs ist in server.go sichtbar, weil Funktionen auf Package-Ebene geteilt werden. Der Import von log wird nicht geteilt, also scheitert der Build:
example.texttext./jobs.go:4:2: undefined: log
Jede Datei listet ihre eigenen Imports, und goimports ergänzt sie für dich. Außerdem kann ein Importname mit einer Deklaration auf Package-Ebene in einer anderen Datei kollidieren. Deklarierst du var log = ... in einer Datei, während eine andere Datei log importiert, bekommst du log already declared through import of package log ("log").
Auch Aliase gelten pro Datei. Benennst du in einer Datei mrand "math/rand/v2" um, hat das keine Auswirkung auf die anderen Dateien im Package.
Go schränkt außerdem ein, welche Packages du überhaupt importieren darfst. Ein Package unter einem Verzeichnis internal/ kann nur von Code importiert werden, der unterhalb des Elternverzeichnisses von internal liegt. Das Go-Keyword package behandelt internal/ und wie Sichtbarkeit zwischen Packages funktioniert.
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 Abhängigkeiten sowie die Modul-Kommandos, die du jeden Tag verwendest, wie go get und go mod tidy. 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
Ist import ein Keyword in Go?
Ja. import ist eines der 25 reservierten Keywords von Go, du kannst es also nicht als Variablen-, Funktions- oder Package-Namen verwenden. Es darf nur in Import-Deklarationen am Anfang einer Datei stehen.
Kann man in Go ein Package innerhalb einer Funktion importieren?
Nein. Imports sind nur auf oberster Ebene einer Datei erlaubt, direkt nach der package-Klausel. Ein import im Rumpf einer Funktion scheitert mit syntax error: unexpected keyword import. Brauchst du ein Package nur in einer Funktion, importierst du es trotzdem am Anfang der Datei.
Wie importiert man ein lokales Package in Go?
Verwende den Modulpfad aus go.mod, gefolgt vom Verzeichnis des Packages. In einem Modul namens example.com/shop importierst du das Package in ./internal/pricing als "example.com/shop/internal/pricing". Go-Module unterstützen keine relativen Imports wie "./pricing".
Was ist der Unterschied zwischen import _ und import . in Go?
import _ "path" führt die Initialisierung des Packages aus, gibt dir aber keine Möglichkeit, es anzusprechen. Das ist für Seiteneffekte gedacht, etwa das Registrieren eines Datenbanktreibers. import . "path" macht das Gegenteil und holt alle exportierten Namen des Packages direkt in deine Datei, sodass du sie ohne Präfix aufrufen kannst. Blank-Imports sind in idiomatischem Go verbreitet, von Dot-Imports wird dagegen abgeraten.
Spielt die Reihenfolge der Imports in Go eine Rolle?
Nein. Dem Compiler ist die Reihenfolge der Zeilen in einem Import-Block egal, und die Initialisierungsreihenfolge ergibt sich aus dem Abhängigkeitsgraphen, nicht daraus, wie du die Imports auflistest. gofmt sortiert sie nach Pfad, und goimports trennt die Standardbibliothek von anderen Packages, aber nur der Lesbarkeit wegen.
Quellen
- The Go Programming Language Specification, Import declarations: https://go.dev/ref/spec#Import_declarations
- The Go Programming Language Specification, Package initialization: https://go.dev/ref/spec#Package_initialization
- Effective Go, The blank identifier in imports: https://go.dev/doc/effective_go#blank_import
- Go FAQ, Unused variables and imports: https://go.dev/doc/faq#unused_variables_and_imports
- Command go, Import path syntax: https://pkg.go.dev/cmd/go#hdr-Import_path_syntax
- Command go, Internal directories: https://pkg.go.dev/cmd/go#hdr-Internal_Directories
- Package database/sql: https://pkg.go.dev/database/sql
- Package embed: https://pkg.go.dev/embed
- Go Code Review Comments, Import blank and import dot: https://go.dev/wiki/CodeReviewComments#import-blank
- goimports: https://pkg.go.dev/golang.org/x/tools/cmd/goimports
- Staticcheck ST1001: https://staticcheck.dev/docs/checks/#ST1001
