Una funzione ti permette di raggruppare il codice in un'unità riutilizzabile.
È composta dalla parola chiave func, dal nome della funzione e da un elenco separato da virgole di zero o più parametri e tipi tra parentesi tonde.
Tutti i parametri devono avere un tipo esplicito; non c'è inferenza di tipo per i parametri. Non ci sono valori predefiniti per i parametri, quindi tutti i parametri della funzione sono obbligatori.
import "fmt"
// No parameters
func PrintHello() {
fmt.Println("Hello")
}
// One parameter
func PrintHelloName(name string) {
fmt.Println("Hello " + name)
}
I parametri dello stesso tipo possono essere dichiarati insieme, seguiti da un'unica dichiarazione di tipo.
import "fmt"
func PrintGreetingName(greeting, name string) {
fmt.Println(greeting + " " + name)
}
Vediamo rapidamente due termini che vengono spesso confusi tra loro: parameters e arguments.
I parametri di una funzione sono i nomi definiti nella firma della funzione, come greeting e name nella funzione PrintGreetingName qui sopra.
Gli argomenti di una funzione sono i valori concreti passati ai parametri della funzione quando la chiamiamo.
Per esempio, nell'esempio qui sotto, "Hello" e "Katrina" sono gli argomenti passati ai parametri greeting e name:
PrintGreetingName("Hello", "Katrina")
Dopo i parametri della funzione vengono zero o più valori restituiti, che devono anch'essi avere un tipo esplicito.
I valori restituiti singoli sono lasciati senza parentesi, mentre quelli multipli sono racchiusi tra parentesi.
I valori vengono restituiti al codice chiamante dalle funzioni usando la parola chiave return.
In una funzione possono esserci più istruzioni return.
L'esecuzione della funzione termina non appena incontra una di queste istruzioni return.
Se una funzione deve restituire più valori, questi sono separati da virgole.
Maggiori informazioni sull'uso idiomatico dei valori restituiti multipli si trovano nel concetto collegato.
func Hello(name string) string {
return "Hello " + name
}
func HelloAndGoodbye(name string) (string, string) {
return "Hello " + name, "Goodbye " + name
}
Per chiamare una funzione, si specifica il nome della funzione e si passano gli argomenti per ciascuno dei suoi parametri tra parentesi.
import "fmt"
// No parameters, no return value
func PrintHello() {
fmt.Println("Hello")
}
// Called like this:
PrintHello()
// One parameter, one return value
func Hello(name string) string {
return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")
// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)
Come per i parametri, anche i valori restituiti possono facoltativamente avere un nome.
Se si usano valori restituiti con nome, un'istruzione return senza argomenti restituirà quei valori.
Questo è noto come return «nudo».
func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
sum, mult = a+b, a*b
sum -= c
mult -= c
return
}
È anche importante chiarire il concetto di passaggio per valore e di passaggio per riferimento.
Prima chiariamo il passaggio per valore.
Nell'esempio qui sotto, quando passiamo la variabile val alla funzione MultiplyByTwo, passiamo una copia di val.
Per questo motivo, newVal ha il valore aggiornato 4, ma la variabile originale val è ancora 2.
Dietro le quinte, Go crea essenzialmente una copia del valore originale, così che solo questa copia, ovvero v, venga modificata dalla funzione.
val := 2
func MultiplyByTwo(v int) int {
v = v * 2
return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function
A rigore, in Go tutti gli argomenti sono passati per valore, cioè viene fatta una copia del valore o dei dati forniti alla funzione.
Ma se non vuoi fare una copia dei dati passati a una funzione e vuoi modificare i dati all'interno della funzione, allora dovresti usare i pointers come argomenti, ovvero il passaggio per riferimento.
Usiamo un pointer per ottenere il passaggio per riferimento.
Passando argomenti di tipo pointer a una funzione, possiamo modificare i dati sottostanti passati alla funzione invece di operare solo su una copia dei dati.
Per ora è sufficiente sapere che i tipi puntatore si riconoscono dal * davanti al tipo nella firma della funzione.
func HandlePointers(x, y *int) {
// Some logic to handle integer values referenced by pointers x and y
}
Se il concetto di pointer ti confonde, non preoccuparti.
Abbiamo una sezione dedicata più avanti nel percorso per aiutarti a padroneggiare i puntatori.
Nota che gli slices e i maps sono eccezioni alla regola appena menzionata.
Quando passiamo uno slice o un map come argomenti a una funzione, vengono trattati come tipi puntatore anche se non c'è un * esplicito nel tipo.
Questo significa che se passiamo uno slice o un map a una funzione e ne modifichiamo i dati sottostanti, le modifiche si rifletteranno sullo slice o sul map originale.
In Go le funzioni sono considerate cittadini di prima classe, il che le rende molto potenti. Ci sono molti altri concetti legati alle funzioni, come i metodi e le funzioni anonime, che incontrerai più avanti nel tuo percorso.