Ogni file Go appartiene a un package, dichiarato sulla sua prima riga. Il codice di un package ne usa un altro importandolo e mettendo il nome del package davanti ai nomi. Solo i nomi che iniziano con la maiuscola si possono usare da fuori del package.
fmt, math e strings sono package della libreria standard. Arrivano insieme a Go, quindi "aggiungerne" uno significa scriverne il nome nel blocco import. Non si scarica niente. fmt.Println, math.Sqrt e strings.Repeat hanno la maiuscola perché sono esportati.
La clausola package
La prima riga di ogni file .go che non sia un commento è package name. Tutti i file di una cartella devono usare lo stesso nome, e insieme formano un package. Una cartella con due nomi di package diversi non compila:
found packages greeting (b.go) and greet (greet.go) in /home/ana/shop/greet
(L'unica eccezione sono i file di test, che possono usare package name_test.)
package main è speciale: produce un eseguibile, e il programma parte dalla sua func main(). Ogni altro package è una libreria pensata per essere importata.
Per convenzione il nome del package corrisponde all'ultimo elemento del percorso della cartella: il codice in shop/greet è package greet. I nomi dei package sono brevi, in minuscolo, parole singole, senza underscore né maiuscole in mezzo: strconv, httptest, greet, non string_utils o greetHelpers.
Importare package
Un percorso di import è una stringa. Per la libreria standard è il percorso del package nell'albero dei sorgenti di Go; per tutto il resto inizia con un percorso di modulo:
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
)
Il percorso è dove vive il package; il nome è quello che scrivi nel codice. Per net/http il nome è http, per math/rand/v2 è rand. Scrivi http.Get, mai net/http.Get.
gofmt e goimports ordinano il blocco e per convenzione separano gli import della libreria standard dagli altri con una riga vuota.
Gli import non usati sono un errore di compilazione:
./main.go:5:2: "os" imported and not used
Gli editor con supporto per Go eseguono goimports al salvataggio, che aggiunge gli import standard mancanti e rimuove quelli non usati al posto tuo.
Nomi esportati e non esportati
In Go la visibilità è decisa da una sola regola: se un nome inizia con una lettera maiuscola, è esportato. Vale per funzioni, tipi, variabili, costanti, campi delle struct e metodi. Non esistono le parole chiave public, private o protected.
| Nome | Visibile fuori dal package? |
|---|---|
func Hello() | sì |
func hello() | no |
type User struct | sì |
campo User.Name | sì |
campo User.email | no |
const MaxSize | sì |
var defaultTimeout | no |
Provare a usare un nome non esportato da un altro package dà un errore undefined:
./main.go:11:20: undefined: greet.prefix
La regola conta anche dentro un singolo file, perché altri package esaminano i tuoi tipi. encoding/json vede solo i campi esportati:
token resta fuori dal JSON, senza avvisi. È una funzionalità quando il campo contiene dati privati, e una sorpresa quando hai dimenticato la maiuscola su un campo che volevi.
Dividere un package su più file
Un package può estendersi su tutti i file che vuoi. Ogni file della cartella con la stessa riga package vede ogni nome dichiarato negli altri, esportato o no, senza importare niente:
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 chiama total direttamente. Non c'è nessun #include e nessun import tra file dello stesso package.
L'errore "undefined" di go run main.go
È il problema sui package più cercato in Go, e ha una sola causa. Eseguire solo il file che contiene main compila solo quel file:
go run main.go
# command-line-arguments
./main.go:6:14: undefined: total
command-line-arguments è il nome che Go dà a un package costruito da un elenco di file, e math.go non è in quell'elenco. Esegui invece l'intero package:
go run .
5
Lo stesso vale per go build main.go. Usa go run . e go build (o go build ./cmd/app) e il problema non si presenta mai. Se persiste con go run ., controlla che entrambi i file dicano package main e che nessuno dei due abbia un vincolo di build (//go:build) o un suffisso _test.go che lo esclude.
I tuoi package
Una sottocartella del tuo modulo è un package separato. Importala con il percorso del modulo preso da go.mod più la cartella:
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
Qui ci sono due cose che fanno inciampare. L'import è il percorso del modulo più la cartella, non un percorso relativo: "./greet" non funziona in modalità moduli. E prefix resta nascosto a main perché è in minuscolo; solo Hello fa parte dell'API del package.
Package internal
Una cartella chiamata internal limita chi può importare ciò che contiene. Il codice in shop/internal/store può essere importato da qualsiasi package che ha radice in shop/, e da niente al di fuori. Un altro modulo che ci prova ottiene:
use of internal package example.com/shop/internal/store not allowed
Usa internal/ per il codice che vuoi condividere tra i tuoi package senza prometterlo al resto del mondo. Puoi cambiarne l'API liberamente, perché nessun codice esterno può dipenderne.
Alias di import
Dai a un import un nome locale diverso scrivendo il nome prima del percorso. Il motivo abituale sono due package con lo stesso nome:
Entrambi i package si chiamano rand, quindi almeno uno ha bisogno di un alias. Usa gli alias per i conflitti e per i nomi poco chiari; non rinominare i package solo per accorciarli, perché chi legge conosce strconv e non il tuo sc.
Import vuoti e import con punto
Import vuoto (_). Importa un package solo per i suoi effetti collaterali: le sue variabili di package e le funzioni init vengono eseguite, ma non usi nessuno dei suoi nomi. I driver di database e i decoder di immagini si registrano così:
import (
"database/sql"
_ "github.com/lib/pq" // registers the "postgres" driver with database/sql
)
Senza il _, l'import sarebbe un errore di import non usato.
Import con punto (.). Mette i nomi esportati del package direttamente nello scope del tuo file, così scrivi Println invece di fmt.Println. Nasconde da dove vengono i nomi ed è sconsigliato, salvo pochi schemi di funzioni di supporto per i test.
Cicli di import
Go non permette a due package di importarsi a vicenda, direttamente o attraverso una catena:
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
L'errore elenca la catena. Due soluzioni coprono quasi tutti i casi:
- Sposta il codice di cui hanno bisogno entrambi i package in un terzo package, di livello più basso, che entrambi importano.
- Se il package A deve solo chiamare qualcosa in B, definisci una piccola interface in A e lascia che il tipo di B la soddisfi. A non importa più B.
Un ciclo di solito significa che i confini dei package sono tracciati attorno a strati di codice che in realtà stanno insieme, quindi è anche un segnale per ripensare la divisione.
Errori comuni
- Eseguire un singolo file.
go run main.goin un package con più file: usago run .. - Import relativi.
import "./utils"fallisce in modalità moduli. Usa"example.com/yourmodule/utils". - Nomi in minuscolo che volevi esportare. Una funzione, un campo o un metodo che serve ad altri package deve iniziare con la maiuscola.
- Nome del package diverso dalla cartella. È permesso, ma confonde: il percorso di import dice
utilse il codice dicehelpers.X. Tienili uguali. - Nomi di package generici.
util,commonemiscnon dicono nulla sul loro contenuto e tendono a diventare calamite per i cicli di import. Dai ai package il nome di ciò che forniscono:money,auth,slug.
Domande frequenti
Perché Go dice che una funzione dello stesso package è undefined?
Probabilmente hai eseguito go run main.go. Indicare un file compila solo quel file, quindi le funzioni definite in altri file dello stesso package mancano: undefined: total. Esegui invece go run ., che compila ogni file .go della cartella. Controlla anche che ogni file abbia la stessa riga package.
Come aggiungo il package fmt in Go?
fmt fa parte della libreria standard e arriva insieme a Go, quindi non c'è niente da installare. Aggiungi import "fmt" dopo la riga package e chiama le sue funzioni con il prefisso fmt., per esempio fmt.Println("hi"). Lo stesso vale per strings, os, time, net/http e ogni altro package standard.
Come importo un mio package in Go?
Importalo con il percorso del modulo più la cartella. In un modulo chiamato example.com/shop, un package nella cartella greet si importa come import "example.com/shop/greet" e si usa come greet.Hello(). Solo i nomi che iniziano con la maiuscola sono visibili a chi importa.
Qual è la differenza tra esportato e non esportato in Go?
Un nome che inizia con una lettera maiuscola (Hello, Total, User.Name) è esportato e si può usare da altri package. Un nome in minuscolo (hello, total, user.name) non è esportato ed è visibile solo dentro il suo package. Non esistono le parole chiave public o private.
Come risolvo "import cycle not allowed" in Go?
Due package si importano a vicenda, direttamente o attraverso una catena, e Go lo vieta. Sposta il codice condiviso in un terzo package che entrambi importano, oppure definisci una piccola interface nel package che ha bisogno del comportamento, così non importa più l'altro.