struct și struct-uri în Go

Grupează mai multe câmpuri cu nume și tipuri diferite într-un singur tip; baza pentru modelarea datelor în Go.

Sintaxă
type Elev struct {
    Nume  string
    Nota  float64
    Email string `json:"email"`
}

e := Elev{Nume: "Ana", Nota: 9.5}
e.Nota = 10

Ce face struct și struct-uri

Un struct adună mai multe valori, numite câmpuri, într-o singură valoare: un elev are nume, notă și email. De obicei i se dă un nume cu type Elev struct { ... }. Câmpurile se accesează cu punct: e.Nume. Câmpurile cu literă mare sunt vizibile din alte pachete, cele cu literă mică nu.

Un struct se creează cu un literal: cu numele câmpurilor (Elev{Nume: "Ana"}, recomandat; câmpurile lipsă primesc valoarea zero) sau pozițional (Elev{"Ana", 9.5, ""}, fragil când se adaugă câmpuri). Struct-urile sunt valori: atribuirea copiază toate câmpurile, iar două struct-uri cu câmpuri comparabile se pot compara cu ==.

Textul dintre apostroafe inverse de după un câmp (`json:"email"`) este un tag: informație citită de pachete precum encoding/json. Struct-urile primesc comportament prin metode și pot include alte struct-uri (embedding). struct{} (fără câmpuri) nu ocupă memorie și se folosește ca semnal în canale sau ca valoare în map-uri folosite ca mulțimi.

Exemple

Exemplul 1: Definire, creare și modificare

%+v afișează și numele câmpurilor.

package main

import "fmt"

type Elev struct {
    Nume  string
    Clasa string
    Nota  float64
}

func main() {
    a := Elev{Nume: "Ana", Clasa: "a X-a B", Nota: 9.5}
    m := Elev{Nume: "Mihai"} // Clasa = "", Nota = 0
    m.Nota = 7.25

    fmt.Printf("%+v\n", a)
    fmt.Printf("%+v\n", m)
    fmt.Println(a.Nume, "are", a.Nota)

    copie := a
    copie.Nota = 4
    fmt.Println("originalul are tot", a.Nota)
}

Rezultat

{Nume:Ana Clasa:a X-a B Nota:9.5}
{Nume:Mihai Clasa: Nota:7.25}
Ana are 9.5
originalul are tot 9.5

Exemplul 2: Slice de struct-uri și struct anonim

package main

import "fmt"

type Produs struct {
    Nume   string
    Pret   float64
    Bucati int
}

func main() {
    cos := []Produs{
        {"pâine", 4.5, 2},
        {"lapte", 7.99, 1},
        {"cașcaval", 32.0, 1},
    }
    total := 0.0
    for _, p := range cos {
        total += p.Pret * float64(p.Bucati)
    }
    fmt.Printf("total: %.2f lei\n", total)

    punct := struct{ X, Y int }{3, 4} // struct fără nume, folosit o dată
    fmt.Println(punct.X, punct.Y)
}

Rezultat

total: 48.99 lei
3 4

Exemplul 3: Tag-uri pentru JSON

omitempty omite câmpul gol; - nu îl scrie niciodată.

package main

import (
    "encoding/json"
    "fmt"
)

type Utilizator struct {
    Nume   string `json:"nume"`
    Email  string `json:"email,omitempty"`
    Parola string `json:"-"`
    Varsta int    `json:"varsta"`
}

func main() {
    u := Utilizator{Nume: "Ioana", Parola: "secret", Varsta: 24}
    date, _ := json.Marshal(u)
    fmt.Println(string(date))
}

Rezultat

{"nume":"Ioana","varsta":24}

Greșeli frecvente

  • Literal pozițional cu mai puține valori decât câmpuri: too few values in struct literal of type Elev. Folosește numele câmpurilor.
  • Modificarea unei copii (de exemplu variabila din for _, p := range cos) crezând că schimbi elementul: scrie cos[i].Pret = ....
  • Tag JSON cu spații sau ghilimele greșite (`json: "nume"`): go vet avertizează struct field tag ... not compatible with reflect.StructTag.Get.

Comenzi înrudite

  • metode: Funcții legate de un tip prin receptor; cu receptor pointer pot modifica valoarea pe care sunt apelate.
  • embedding (încorporare): Include un tip în altul fără nume de câmp; câmpurile și metodele lui devin accesibile direct (compoziție).
  • pointeri (* și &): Un pointer (*T) ține adresa unei variabile; prin el funcțiile pot modifica valoarea originală.
  • vizibilitate (exportat/neexportat): Numele care încep cu literă mare sunt vizibile din alte pachete; cele cu literă mică rămân private pachetului.
  • type: Definește un tip nou (pe baza altuia, struct, interfață, funcție) sau un alias pentru un tip existent.