Strutture (Struct) in Go
Le struct (strutture) in Go sono tipi composti che raggruppano insieme campi con nome. Sono il meccanismo principale per definire tipi personalizzati e rappresentano il fondamento della programmazione orientata agli oggetti in Go, che non ha classi. In questa guida esploreremo la definizione, la creazione e l’utilizzo delle struct.
Definizione di una Struct
Una struct viene definita con la parola chiave type seguita dal nome e dalla lista dei campi.
package main
import "fmt"
type Persona struct {
Nome string
Cognome string
Eta int
}
func main() {
var p Persona
fmt.Println(p) // { 0} - campi inizializzati ai valori zero
}
I campi vengono inizializzati al valore zero del loro tipo: "" per le stringhe, 0 per i numeri, false per i booleani, nil per puntatori e slice.
Creazione di Istanze
Esistono diversi modi per creare un’istanza di una struct.
// 1. Specificando i campi per nome (consigliato)
p1 := Persona{
Nome: "Marco",
Cognome: "Rossi",
Eta: 30,
}
// 2. Specificando i valori in ordine (sconsigliato per struct grandi)
p2 := Persona{"Anna", "Bianchi", 25}
// 3. Dichiarazione e assegnazione separata
var p3 Persona
p3.Nome = "Luigi"
p3.Cognome = "Verdi"
p3.Eta = 35
// 4. Puntatore a struct con new
p4 := new(Persona)
p4.Nome = "Giulia"
// 5. Puntatore a struct con letterale
p5 := &Persona{
Nome: "Sara",
Cognome: "Neri",
Eta: 28,
}
La forma con campi nominati e preferita perche e piu leggibile e resistente ai cambiamenti nella definizione della struct.
Accesso ai Campi
I campi si accedono usando l’operatore punto ..
p := Persona{Nome: "Marco", Cognome: "Rossi", Eta: 30}
fmt.Println(p.Nome) // Marco
fmt.Println(p.Cognome) // Rossi
fmt.Println(p.Eta) // 30
// Modifica di un campo
p.Eta = 31
fmt.Println(p.Eta) // 31
Go dereferenzia automaticamente i puntatori a struct:
pp := &Persona{Nome: "Anna", Cognome: "Bianchi", Eta: 25}
fmt.Println(pp.Nome) // Anna (equivalente a (*pp).Nome)
Struct Anonime
Le struct anonime sono utili per strutture temporanee che non necessitano di un tipo dedicato.
punto := struct {
X, Y float64
}{
X: 3.5,
Y: 7.2,
}
fmt.Printf("Punto: (%.1f, %.1f)\n", punto.X, punto.Y)
Le struct anonime sono comuni nei test e nelle configurazioni temporanee.
Struct Annidate
Una struct puo contenere campi di tipo struct, creando strutture annidate.
type Indirizzo struct {
Via string
Citta string
CAP string
}
type Dipendente struct {
Nome string
Cognome string
Indirizzo Indirizzo
Stipendio float64
}
func main() {
d := Dipendente{
Nome: "Marco",
Cognome: "Rossi",
Indirizzo: Indirizzo{
Via: "Via Roma 1",
Citta: "Milano",
CAP: "20100",
},
Stipendio: 35000,
}
fmt.Printf("%s abita a %s\n", d.Nome, d.Indirizzo.Citta)
}
Embedding (Incorporamento)
Go supporta l’embedding di struct: un campo senza nome il cui tipo e un’altra struct. I campi della struct incorporata sono accessibili direttamente.
type Animale struct {
Nome string
Eta int
}
type Cane struct {
Animale // Struct incorporata (embedding)
Razza string
}
func main() {
c := Cane{
Animale: Animale{
Nome: "Rex",
Eta: 5,
},
Razza: "Pastore Tedesco",
}
// Accesso diretto ai campi dell'Animale
fmt.Println(c.Nome) // Rex (invece di c.Animale.Nome)
fmt.Println(c.Eta) // 5
fmt.Println(c.Razza) // Pastore Tedesco
}
L’embedding non e ereditarieta ma composizione: il Cane non “e un” Animale, ma “ha un” Animale.
Confronto tra Struct
Le struct possono essere confrontate con == e != se tutti i loro campi sono confrontabili.
type Punto struct {
X, Y int
}
a := Punto{1, 2}
b := Punto{1, 2}
c := Punto{3, 4}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
Le struct con campi non confrontabili (come slice o map) non possono essere confrontate con ==.
Tag delle Struct
I tag sono metadati associati ai campi di una struct. Sono ampiamente usati per la serializzazione JSON, la validazione e i database.
package main
import (
"encoding/json"
"fmt"
)
type Utente struct {
ID int `json:"id"`
Nome string `json:"nome"`
Email string `json:"email"`
Password string `json:"-"` // Escluso dal JSON
Ruolo string `json:"ruolo,omitempty"` // Omesso se vuoto
}
func main() {
u := Utente{
ID: 1,
Nome: "Marco",
Email: "marco@example.com",
Password: "segreto123",
}
datiJSON, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(datiJSON))
}
Output:
{
"id": 1,
"nome": "Marco",
"email": "marco@example.com"
}
Il tag json:"-" esclude il campo dalla serializzazione. omitempty omette il campo se ha il valore zero.
Costruttori (Funzioni Factory)
Go non ha costruttori nativi. Per convenzione, si creano funzioni New che restituiscono un puntatore alla struct.
type Server struct {
Host string
Porta int
Timeout int
}
func NewServer(host string, porta int) *Server {
return &Server{
Host: host,
Porta: porta,
Timeout: 30, // Valore predefinito
}
}
func main() {
s := NewServer("localhost", 8080)
fmt.Printf("Server su %s:%d\n", s.Host, s.Porta)
}
Struct e Puntatori
Passare struct per puntatore evita copie e permette le modifiche.
func compleanno(p *Persona) {
p.Eta++
}
func main() {
p := Persona{Nome: "Marco", Eta: 30}
compleanno(&p)
fmt.Println(p.Eta) // 31
}
Conclusione
Le struct sono il tipo composto fondamentale in Go e sostituiscono le classi presenti in altri linguaggi. L’embedding offre composizione senza ereditarieta, i tag forniscono metadati per la serializzazione e la convenzione dei costruttori New mantiene il codice organizzato. Comprendere le struct e essenziale per padroneggiare Go, poiche metodi, interfacce e gran parte della libreria standard si basano su di esse.