Dichiarare una funzione
Una dichiarazione di funzione è composta da func, un nome, i parametri, il tipo del risultato e un corpo. Il tipo va dopo il nome del parametro, non prima.
add riceve due valori int e restituisce un int. greet non restituisce niente, quindi la sua firma non ha un tipo di risultato e il suo corpo non ha bisogno di return.
Una funzione che dichiara un risultato deve terminare con un'istruzione di chiusura. Se qualche percorso può raggiungere la parentesi graffa finale senza ritornare, il compilatore si ferma con missing return.
Parametri che condividono un tipo
Parametri consecutivi dello stesso tipo possono condividere un unico nome di tipo. func add(a, b int) int significa esattamente la stessa cosa di func add(a int, b int) int. Si possono anche mescolare:
func scale(x, y float64, factor int) (float64, float64)
Qui x e y sono float64 e factor è un int.
Valori di ritorno
Una funzione può restituire zero, uno o più valori. Più risultati vanno tra parentesi, e il chiamante li riceve con un'assegnazione multipla:
Stampa 3 2. La coppia (value, error) è l'uso più comune di questa funzionalità in Go, e ha una pagina dedicata sui valori di ritorno multipli.
Gli argomenti vengono copiati
Go passa ogni argomento per valore. La funzione lavora su una copia, quindi assegnare un valore a un parametro non modifica mai la variabile del chiamante.
L'output è:
{1 2}
{100 2}
[99 2 3]
La terza riga sorprende molti. Un valore slice è una piccola intestazione (puntatore, lunghezza, capacità). La copia punta allo stesso array sottostante, quindi scrivere s[0] scrive l'elemento del chiamante. Però append dentro la funzione può allocare un nuovo array, e il chiamante non vede mai quella nuova intestazione. Se una funzione fa crescere uno slice, restituisci il nuovo slice. La stessa logica vale per le mappe: la funzione può aggiungere chiavi che il chiamante vede, perché il valore map fa riferimento a dati condivisi.
Usa un parametro puntatore quando la funzione deve modificare la variabile del chiamante, oppure quando il valore è una struct grande che non vuoi copiare a ogni chiamata.
Le funzioni sono valori
Una funzione ha un tipo, scritto senza il nome: func(int, int) int. Puoi memorizzare una funzione in una variabile, metterla in una mappa o in uno slice e passarla a un'altra funzione.
square è una funzione anonima assegnata a una variabile. Le funzioni anonime possono anche leggere e modificare variabili dello scope circostante, il che le rende closure.
Un tipo funzione con nome rende le firme più leggibili quando la stessa forma compare in molti punti:
type Transform func(int) int
func apply(xs []int, f Transform) []int
Il valore zero di un tipo funzione è nil. Chiamare una funzione nil provoca il panic invalid memory address or nil pointer dereference, quindi controlla le callback opzionali prima di chiamarle. I valori funzione si possono confrontare solo con nil, mai tra loro.
Ricorsione
Una funzione può chiamare sé stessa. Go non ha l'ottimizzazione delle chiamate in coda, ma gli stack delle goroutine crescono quando serve, quindi le normali profondità di ricorsione non sono un problema.
for i := range 6 conta da 0 a 5 e richiede Go 1.22 o successivo.
Cosa non hanno le funzioni Go
- Niente overloading. Due funzioni nello stesso package non possono avere lo stesso nome, neanche con parametri diversi.
- Niente argomenti predefiniti o con nome. Ogni parametro viene passato a ogni chiamata, nell'ordine. Una struct di configurazione ti dà campi con nome e facoltativi:
NewServer(Config{Port: 8080}). - Niente funzioni con nome annidate. Dentro il corpo di una funzione puoi dichiarare solo funzioni anonime, di solito assegnate a una variabile.
Le liste di argomenti di lunghezza variabile sono supportate tramite le funzioni variadiche.
Funzioni esportate e non esportate
Una funzione il cui nome inizia con una lettera maiuscola (Parse) è esportata e si può chiamare da altri package. Un nome in minuscolo (parse) è visibile solo dentro il proprio package. Non esistono parole chiave public o private; la maiuscola è l'intera regola.
Errori comuni
- Aspettarsi che la modifica di un parametro arrivi al chiamante. Assegnare un valore a un parametro modifica solo la copia. Restituisci il nuovo valore o accetta un puntatore.
- Fare append dentro una funzione e ignorare il risultato.
func add(s []int) { s = append(s, 1) }non ha effetto sulla lunghezza dello slice del chiamante. Scrivifunc add(s []int) []inte usas = add(s). - Risultati inutilizzati. Go permette di ignorare un valore di ritorno.
go vetnon segnala un errore scartato, ma linter comeerrcheck(parte digolangci-lint) sì. Usa_ =solo quando ignorarlo è una scelta.
Domande frequenti
Come si definisce una funzione in Go?
Usa la parola chiave func, un nome, una lista di parametri con i tipi dopo i nomi e il tipo del risultato: func add(a, b int) int { return a + b }. Una funzione senza risultato omette il tipo, e una funzione con più risultati li elenca tra parentesi: func divmod(a, b int) (int, int).
Go passa gli argomenti per valore o per riferimento?
Sempre per valore. La funzione riceve una copia di ogni argomento. Per un int o una struct quella copia è indipendente dalla variabile del chiamante. Slice, mappe, channel e puntatori sono piccoli valori che fanno riferimento a dati condivisi, quindi le modifiche agli elementi a cui puntano sono visibili al chiamante, mentre riassegnare il parametro stesso non lo è. Passa un puntatore (*T) quando la funzione deve modificare la variabile del chiamante.
Si può passare una funzione come parametro in Go?
Sì. Le funzioni sono valori con un tipo come func(int) int. Dichiara un parametro di quel tipo e passa qualsiasi funzione con una firma corrispondente, con nome o anonima: func apply(xs []int, f func(int) int).
Go supporta l'overloading delle funzioni o i parametri predefiniti?
No. Ogni nome di funzione in un package deve essere unico, e ogni parametro va passato a ogni chiamata. I sostituti abituali sono nomi distinti (NewServer, NewServerWithTLS), una struct di configurazione, i parametri variadici o le opzioni funzionali.