cat swift/mutabilita-immutabilita-copia.md

Lezione 0335 min

Mutabilità, immutabilità e copia

Come Swift usa let, var e semantica di copia per rendere più leggibile il cambiamento.

Swift prende molto sul serio una domanda semplice: questo valore deve cambiare?

La risposta è visibile nella sintassi:

let nome = "Ada"
var tentativi = 0

let dichiara una costante. var dichiara una variabile. La differenza non è cosmetica: comunica al compilatore e a chi legge il codice se quel valore fa parte di uno stato stabile o di uno stato in evoluzione.

La mutabilità come informazione

In un programma reale, troppa mutabilità rende difficile capire cosa può cambiare e quando.

Questo codice è tecnicamente semplice:

var totale = 0
totale = totale + 1000
totale = totale + 2500
totale = totale - 500

Ma se il calcolo cresce, il valore di totale diventa una storia da ricostruire passo dopo passo.

A volte è più chiaro descrivere il risultato come trasformazione:

let prezzi = [1000, 2500]
let sconto = 500
let totale = prezzi.reduce(0, +) - sconto

Non sempre questa versione è migliore, ma mostra un principio: meno stati intermedi mutabili, meno superfici per errori.

let non significa “immutabilità profonda” per le classi

Con le struct, let rende immutabile il valore.

struct Prodotto {
  var nome: String
}

let prodotto = Prodotto(nome: "Libro")
// prodotto.nome = "Quaderno" // errore

Con le class, invece, let rende costante il riferimento, non necessariamente l’oggetto.

final class Sessione {
  var token: String

  init(token: String) {
    self.token = token
  }
}

let sessione = Sessione(token: "abc")
sessione.token = "xyz" // consentito

Questa distinzione è fondamentale. let sessione significa: la variabile sessione punterà sempre allo stesso oggetto. Non significa: l’oggetto non può cambiare.

Copiare non è sempre duplicare subito

Swift usa spesso ottimizzazioni interne come il copy-on-write. Per molti tipi standard, come Array, copiare un valore non significa necessariamente duplicare subito tutta la memoria.

var a = [1, 2, 3]
var b = a

b.append(4)

Mentalmente puoi ragionare come se b fosse una copia indipendente. Swift e le sue librerie si occupano di evitare copie costose finché possibile.

Questo è un punto bello del linguaggio: ti offre una semantica semplice, mentre sotto può ottimizzare.

Mutating: cambiare se stessi

Le struct sono value types. Quando un metodo cambia una proprietà della struct, deve dichiararlo:

struct Carrello {
  private(set) var prodotti: [String] = []

  mutating func aggiungi(_ prodotto: String) {
    prodotti.append(prodotto)
  }
}

mutating è una promessa esplicita: questo metodo modifica il valore.

Da fuori:

var carrello = Carrello()
carrello.aggiungi("Libro")

Se carrello fosse dichiarato con let, il metodo non potrebbe essere chiamato:

let carrelloBloccato = Carrello()
// carrelloBloccato.aggiungi("Libro") // errore

Questo rende evidente dove il programma consente cambiamenti.

Disegnare API con cambiamento esplicito

Hai due modi principali per modellare una trasformazione.

Il primo modifica il valore:

struct Carrello {
  var righe: [String]

  mutating func aggiungi(_ prodotto: String) {
    righe.append(prodotto)
  }
}

Il secondo restituisce un nuovo valore:

struct Carrello {
  let righe: [String]

  func aggiungendo(_ prodotto: String) -> Carrello {
    Carrello(righe: righe + [prodotto])
  }
}

Non c’è una risposta universale. La prima forma è comoda quando lavori localmente con uno stato. La seconda è utile quando vuoi trasformazioni più prevedibili, testabili e facilmente componibili.

Esempio: carrello immutabile

struct Prodotto {
  let nome: String
  let prezzoInCent: Int
}

struct Carrello {
  let prodotti: [Prodotto]

  var totaleInCent: Int {
    prodotti.reduce(0) { totale, prodotto in
      totale + prodotto.prezzoInCent
    }
  }

  func aggiungendo(_ prodotto: Prodotto) -> Carrello {
    Carrello(prodotti: prodotti + [prodotto])
  }
}

let vuoto = Carrello(prodotti: [])
let conLibro = vuoto.aggiungendo(Prodotto(nome: "Libro", prezzoInCent: 1900))

print(vuoto.totaleInCent)    // 0
print(conLibro.totaleInCent) // 1900

Il valore originale resta intatto. Questo stile è spesso utile nei test e nella logica di dominio.

Errore comune

Un errore frequente è dichiarare tutto var “per comodità”.

var nome = "Libro"
var prezzoInCent = 1900
var categoria = "libri"

Se qualcosa non deve cambiare, dichiaralo let. Non è solo una protezione tecnica: è documentazione eseguibile.

Checklist

Quando scrivi una proprietà o una variabile, chiediti:

  • questo valore deve cambiare dopo l’inizializzazione?
  • chi è autorizzato a cambiarlo?
  • il cambiamento è locale oppure visibile altrove?
  • una funzione che restituisce un nuovo valore sarebbe più chiara?
  • sto usando var solo per abitudine?

Esercizio

Riscrivi Carrello in due versioni:

  1. versione mutabile con mutating func aggiungi;
  2. versione immutabile con func aggiungendo.

Poi scrivi tre test manuali con print:

  • il carrello vuoto resta vuoto;
  • il carrello modificato contiene il prodotto;
  • il totale viene calcolato correttamente.