Valori di Ritorno delle Funzioni in Go
I valori di ritorno delle funzioni in Go offrono funzionalita uniche rispetto a molti altri linguaggi. La possibilita di restituire piu valori e alla base del modello idiomatico di gestione degli errori di Go. In questa guida esploreremo i ritorni singoli, multipli, con nome e i pattern piu comuni.
Ritorno Singolo
La forma piu semplice: una funzione che restituisce un singolo valore.
package main
import "fmt"
func quadrato(n int) int {
return n * n
}
func main() {
risultato := quadrato(7)
fmt.Println("Il quadrato di 7 e:", risultato) // 49
}
Il tipo di ritorno viene dichiarato dopo la lista dei parametri.
Ritorni Multipli
Go permette di restituire piu valori da una funzione. I tipi dei valori di ritorno vengono racchiusi tra parentesi.
package main
import (
"fmt"
"math"
)
func coordinate(angolo float64) (float64, float64) {
x := math.Cos(angolo)
y := math.Sin(angolo)
return x, y
}
func main() {
x, y := coordinate(math.Pi / 4)
fmt.Printf("x=%.4f, y=%.4f\n", x, y)
}
I valori vengono restituiti separati da virgola e assegnati a variabili corrispondenti al momento della chiamata.
Valori di Ritorno con Nome
I valori di ritorno possono avere un nome. Queste variabili sono inizializzate al valore zero del loro tipo e possono essere utilizzate nel corpo della funzione.
func divisioneConResto(dividendo, divisore int) (quoziente, resto int) {
quoziente = dividendo / divisore
resto = dividendo % divisore
return // naked return: restituisce quoziente e resto
}
func main() {
q, r := divisioneConResto(17, 5)
fmt.Printf("17 / 5 = %d con resto %d\n", q, r) // 3 con resto 2
}
Il return senza argomenti (naked return) restituisce automaticamente le variabili nominate. Questo e consigliato solo in funzioni brevi per mantenere la leggibilita.
Ritorni con Nome: Vantaggi nella Documentazione
I nomi dei valori di ritorno appaiono nella documentazione generata da go doc, rendendo l’API piu comprensibile.
// MinMax restituisce il valore minimo e massimo di una slice di interi.
func MinMax(valori []int) (minimo, massimo int) {
if len(valori) == 0 {
return 0, 0
}
minimo = valori[0]
massimo = valori[0]
for _, v := range valori[1:] {
if v < minimo {
minimo = v
}
if v > massimo {
massimo = v
}
}
return
}
Leggendo la firma della funzione, il programmatore capisce subito cosa rappresenta ciascun valore restituito.
Il Pattern Errore come Secondo Valore
Il pattern idiomatico piu importante in Go: le funzioni che possono fallire restituiscono l’errore come ultimo valore di ritorno.
package main
import (
"fmt"
"strconv"
)
func convertiInIntero(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("conversione fallita per %q: %w", s, err)
}
return n, nil
}
func main() {
// Caso di successo
valore, err := convertiInIntero("42")
if err != nil {
fmt.Println("Errore:", err)
return
}
fmt.Println("Valore:", valore)
// Caso di errore
valore2, err := convertiInIntero("abc")
if err != nil {
fmt.Println("Errore:", err)
return
}
fmt.Println("Valore:", valore2)
}
Questo pattern sostituisce le eccezioni di altri linguaggi con un meccanismo esplicito e prevedibile.
Gestione degli Errori con If e Short Statement
La combinazione della short statement con il controllo dell’errore e un pattern estremamente frequente.
import "os"
func leggiFile(percorso string) ([]byte, error) {
if contenuto, err := os.ReadFile(percorso); err != nil {
return nil, fmt.Errorf("impossibile leggere %s: %w", percorso, err)
} else {
return contenuto, nil
}
}
Oppure, nella forma piu idiomatica con ritorno anticipato:
func leggiFile(percorso string) ([]byte, error) {
contenuto, err := os.ReadFile(percorso)
if err != nil {
return nil, fmt.Errorf("impossibile leggere %s: %w", percorso, err)
}
return contenuto, nil
}
Ignorare i Valori di Ritorno con _
Quando un valore di ritorno non e necessario, si usa il blank identifier _.
// Ignora l'errore (da usare con cautela)
contenuto, _ := os.ReadFile("config.txt")
// Ignora il primo valore
_, errore := fmt.Println("test")
// Ignora tutti i valori (raro ma valido)
// fmt.Println restituisce (int, error)
fmt.Println("Questo stampa e basta")
Nota: ignorare gli errori e generalmente sconsigliato nella programmazione Go. E una pratica accettabile solo in contesti dove il fallimento e innocuo.
Funzioni che Restituiscono Solo un Errore
Alcune funzioni eseguono un’azione e restituiscono solo un eventuale errore.
import "os"
func scriviFile(percorso, contenuto string) error {
err := os.WriteFile(percorso, []byte(contenuto), 0644)
if err != nil {
return fmt.Errorf("scrittura fallita: %w", err)
}
return nil
}
func main() {
if err := scriviFile("output.txt", "Ciao mondo"); err != nil {
fmt.Println("Errore:", err)
return
}
fmt.Println("File scritto con successo")
}
Ritorno Anticipato per Chiarezza
Il pattern del ritorno anticipato migliora la leggibilita evitando nidificazioni eccessive.
func elaboraDati(dati []byte) (string, error) {
if len(dati) == 0 {
return "", fmt.Errorf("dati vuoti")
}
if dati[0] != '{' {
return "", fmt.Errorf("formato non valido")
}
// Logica principale senza nidificazione
risultato := string(dati)
return risultato, nil
}
Conclusione
Il sistema di valori di ritorno multipli di Go e il fondamento della gestione degli errori nel linguaggio. Il pattern (valore, error) e idiomatico e universalmente adottato nella comunita Go. I ritorni con nome migliorano la documentazione e i naked return semplificano funzioni brevi. Padroneggiare questi pattern e essenziale per scrivere codice Go robusto e leggibile.