Menu

Golang Pakete und Imports: exportierte Namen, mehrere Dateien

Wie Go-Pakete funktionieren: die package-Klausel, Standard- und Modulpakete importieren, exportierte vs nicht exportierte Namen, ein Paket auf mehrere Dateien verteilen, interne Pakete, Import-Aliase, Blank Imports und der „undefined“-Fehler bei go run main.go.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Jede Go-Datei gehört zu einem Paket, das in ihrer ersten Zeile deklariert wird. Code in einem Paket nutzt ein anderes, indem er es importiert und Namen mit dem Paketnamen versieht. Nur Namen, die mit einem Großbuchstaben beginnen, lassen sich von außerhalb des Pakets nutzen.

fmt, math und strings sind Pakete der Standardbibliothek. Sie werden mit Go ausgeliefert, also heißt eins „hinzufügen“, seinen Namen in den import-Block zu schreiben. Heruntergeladen wird nichts. fmt.Println, math.Sqrt und strings.Repeat sind großgeschrieben, weil sie exportiert sind.

Die package-Klausel

Die erste Zeile jeder .go-Datei, die kein Kommentar ist, lautet package name. Alle Dateien in einem Verzeichnis müssen denselben Namen verwenden und bilden zusammen ein Paket. Ein Verzeichnis mit zwei verschiedenen Paketnamen kompiliert nicht:

found packages greeting (b.go) and greet (greet.go) in /home/ana/shop/greet

(Die einzige Ausnahme sind Testdateien, die package name_test verwenden dürfen.)

package main ist besonders: Es erzeugt eine ausführbare Datei, und das Programm startet bei seinem func main(). Jedes andere Paket ist eine Bibliothek, die importiert werden soll.

Per Konvention entspricht der Paketname dem letzten Element des Verzeichnispfads: Code in shop/greet ist package greet. Paketnamen sind kurze, kleingeschriebene Einzelwörter ohne Unterstriche oder gemischte Großschreibung: strconv, httptest, greet, nicht string_utils oder greetHelpers.

Pakete importieren

Ein Importpfad ist ein String. Bei der Standardbibliothek ist es der Pfad des Pakets im Quellbaum von Go; bei allem anderen beginnt er mit einem Modulpfad:

import (
	"fmt"           // standard library
	"net/http"      // standard library, nested path
	"math/rand/v2"  // standard library, version 2 of math/rand

	"github.com/google/uuid"   // another module (needs go get)
	"example.com/shop/greet"   // a package in your own module
)

Der Pfad ist, wo das Paket liegt; der Name ist, was du im Code tippst. Bei net/http ist der Name http, bei math/rand/v2 ist er rand. Du schreibst http.Get, nie net/http.Get.

gofmt und goimports sortieren den Block und trennen per Konvention Imports aus der Standardbibliothek durch eine Leerzeile vom Rest.

Ungenutzte Imports sind ein Compilerfehler:

./main.go:5:2: "os" imported and not used

Editoren mit Go-Unterstützung führen beim Speichern goimports aus, das fehlende Standard-Imports für dich ergänzt und ungenutzte entfernt.

Exportierte vs nicht exportierte Namen

Die Sichtbarkeit wird in Go durch eine einzige Regel bestimmt: Beginnt ein Name mit einem Großbuchstaben, ist er exportiert. Das gilt für Funktionen, Typen, Variablen, Konstanten, Struct-Felder und Methoden. Schlüsselwörter wie public, private oder protected gibt es nicht.

NameAußerhalb des Pakets sichtbar?
func Hello()ja
func hello()nein
type User structja
Feld User.Nameja
Feld User.emailnein
const MaxSizeja
var defaultTimeoutnein

Einen nicht exportierten Namen aus einem anderen Paket zu benutzen ergibt einen undefined-Fehler:

./main.go:11:20: undefined: greet.prefix

Die Regel zählt sogar innerhalb einer einzigen Datei, weil andere Pakete deine Typen untersuchen. encoding/json sieht nur exportierte Felder:

token fehlt im JSON, stillschweigend. Das ist ein Feature, wenn das Feld private Daten enthält, und eine Überraschung, wenn du vergessen hast, ein gewünschtes Feld großzuschreiben.

Ein Paket auf mehrere Dateien verteilen

Ein Paket kann sich über beliebig viele Dateien erstrecken. Jede Datei im Verzeichnis mit derselben package-Zeile sieht jeden Namen, der in den anderen deklariert ist, exportiert oder nicht, ohne etwas zu importieren:

shop/
├── go.mod        (module example.com/shop)
├── main.go
└── math.go
// main.go
package main

import "fmt"

func main() {
	fmt.Println(total(2, 3))
}
// math.go
package main

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

main.go ruft total direkt auf. Es gibt kein #include und keinen Import zwischen Dateien desselben Pakets.

Der „undefined“-Fehler bei go run main.go

Das ist das meistgesuchte Paketproblem in Go, und es hat eine einzige Ursache. Führst du nur die Datei aus, die main enthält, wird nur diese Datei kompiliert:

go run main.go
# command-line-arguments
./main.go:6:14: undefined: total

command-line-arguments ist der Name, den Go einem Paket gibt, das aus einer Liste von Dateien gebaut wird, und math.go steht nicht in dieser Liste. Führ stattdessen das ganze Paket aus:

go run .
5

Dasselbe gilt für go build main.go. Nimm go run . und go build (oder go build ./cmd/app), und das Problem taucht nie auf. Bleibt es bei go run . bestehen, prüf, dass beide Dateien package main enthalten und keine davon durch ein Build-Constraint (//go:build) oder das Suffix _test.go ausgeschlossen wird.

Eigene Pakete

Ein Unterverzeichnis deines Moduls ist ein eigenes Paket. Importiere es über den Modulpfad aus go.mod plus das Verzeichnis:

shop/
├── go.mod          (module example.com/shop)
├── main.go
└── greet/
    └── greet.go
// greet/greet.go
package greet

const prefix = "Hi, "

// Hello returns a greeting for name.
func Hello(name string) string {
	return prefix + name
}
// main.go
package main

import (
	"fmt"

	"example.com/shop/greet"
)

func main() {
	fmt.Println(greet.Hello("Ana"))
}
Hi, Ana

Zwei Dinge bringen hier viele ins Stolpern. Der Import ist Modulpfad plus Verzeichnis, kein relativer Pfad: "./greet" funktioniert im Modulmodus nicht. Und prefix bleibt für main unsichtbar, weil es kleingeschrieben ist; nur Hello gehört zur API des Pakets.

Interne Pakete

Ein Verzeichnis namens internal schränkt ein, wer importieren darf, was darin liegt. Code in shop/internal/store kann von jedem Paket unterhalb von shop/ importiert werden und von nichts außerhalb. Ein anderes Modul, das es versucht, bekommt:

use of internal package example.com/shop/internal/store not allowed

Nimm internal/ für Code, den du zwischen deinen eigenen Paketen teilen willst, ohne ihn dem Rest der Welt zu versprechen. Seine API kannst du frei ändern, da kein Code von außen davon abhängen kann.

Import-Aliase

Du gibst einem Import einen anderen lokalen Namen, indem du den Namen vor den Pfad schreibst. Der übliche Grund sind zwei Pakete mit demselben Namen:

Beide Pakete heißen rand, also braucht mindestens eins einen Alias. Nimm Aliase bei Konflikten und für unklare Namen; benenne Pakete nicht nur zum Abkürzen um, denn Leser kennen strconv und nicht dein sc.

Blank Imports und Dot Imports

Blank Import (_). Importiert ein Paket nur wegen seiner Seiteneffekte: Seine Paketvariablen und init-Funktionen laufen, aber du nutzt keinen seiner Namen. Datenbanktreiber und Bild-Decoder registrieren sich auf diese Weise:

import (
	"database/sql"

	_ "github.com/lib/pq" // registers the "postgres" driver with database/sql
)

Ohne das _ wäre der Import ein Fehler wegen eines ungenutzten Imports.

Dot Import (.). Holt die exportierten Namen des Pakets direkt in den Scope deiner Datei, sodass du Println statt fmt.Println schreibst. Das verschleiert, woher Namen kommen, und außerhalb weniger Muster für Test-Helfer wird davon abgeraten.

Import-Zyklen

Go erlaubt nicht, dass sich zwei Pakete gegenseitig importieren, weder direkt noch über eine Kette:

package example.com/shop
	imports example.com/shop/greet from main.go
	imports example.com/shop/other from c.go
	imports example.com/shop/greet from o.go: import cycle not allowed

Der Fehler listet die Kette auf. Zwei Lösungen decken fast jeden Fall ab:

  1. Verschieb den Code, den beide Pakete brauchen, in ein drittes, tiefer liegendes Paket, das beide importieren.
  2. Muss Paket A nur etwas in B aufrufen, definier in A ein kleines Interface und lass den Typ aus B es erfüllen. Dann importiert A das Paket B nicht mehr.

Ein Zyklus bedeutet meist, dass die Paketgrenzen um Code-Schichten gezogen sind, die eigentlich zusammengehören. Er ist also auch ein Signal, die Aufteilung zu überdenken.

Häufige Fehler

  • Eine einzelne Datei ausführen. go run main.go in einem Paket aus mehreren Dateien: Nimm go run ..
  • Relative Imports. import "./utils" scheitert im Modulmodus. Nimm "example.com/yourmodule/utils".
  • Kleingeschriebene Namen, die du exportieren wolltest. Eine Funktion, ein Feld oder eine Methode, die andere Pakete brauchen, muss mit einem Großbuchstaben beginnen.
  • Paketname passt nicht zum Verzeichnis. Erlaubt, aber verwirrend: Der Importpfad sagt utils, und der Code sagt helpers.X. Halte beide gleich.
  • Sammelnamen für Pakete. util, common und misc sagen nichts über ihren Inhalt und werden gern zu Magneten für Import-Zyklen. Benenne Pakete nach dem, was sie bereitstellen: money, auth, slug.

Häufig gestellte Fragen

Warum sagt Go, dass eine Funktion im selben Paket undefined ist?

Du hast wahrscheinlich go run main.go ausgeführt. Nennst du eine Datei, wird nur diese kompiliert, also fehlen Funktionen, die in anderen Dateien desselben Pakets definiert sind: undefined: total. Führ stattdessen go run . aus, das jede .go-Datei im Verzeichnis kompiliert. Prüf auch, ob jede Datei dieselbe package-Zeile hat.

Wie füge ich in Go das Paket fmt hinzu?

fmt gehört zur Standardbibliothek und wird mit Go ausgeliefert, also gibt es nichts zu installieren. Schreib import "fmt" hinter die package-Zeile und ruf seine Funktionen mit dem Präfix fmt. auf, zum Beispiel fmt.Println("hi"). Dasselbe gilt für strings, os, time, net/http und jedes andere Standardpaket.

Wie importiere ich in Go mein eigenes Paket?

Importiere es über den Modulpfad plus Verzeichnis. In einem Modul namens example.com/shop importierst du ein Paket im Ordner greet als import "example.com/shop/greet" und nutzt es als greet.Hello(). Nur Namen, die mit einem Großbuchstaben beginnen, sind für den Importierenden sichtbar.

Was ist der Unterschied zwischen exportiert und nicht exportiert in Go?

Ein Name, der mit einem Großbuchstaben beginnt (Hello, Total, User.Name), ist exportiert und kann aus anderen Paketen genutzt werden. Ein kleingeschriebener Name (hello, total, user.name) ist nicht exportiert und nur im eigenen Paket sichtbar. Schlüsselwörter wie public oder private gibt es nicht.

Wie behebe ich „import cycle not allowed“ in Go?

Zwei Pakete importieren sich gegenseitig, direkt oder über eine Kette, und das verbietet Go. Verschieb den gemeinsamen Code in ein drittes Paket, das beide importieren, oder definier ein kleines Interface in dem Paket, das das Verhalten braucht, damit es das andere nicht mehr importiert.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S