cat swift/sendable-data-race-safety.md
Sendable e data-race safety
Swift 6 porta la sicurezza dai data race nel linguaggio: Sendable descrive valori sicuri da attraversare tra domini concorrenti.
Swift 6 ha reso la data-race safety una parte centrale del linguaggio. L’obiettivo è far emergere potenziali accessi concorrenti non sicuri prima della produzione.
Uno dei concetti chiave è Sendable.
Cosa significa Sendable
Un tipo Sendable può essere trasferito in modo sicuro tra domini concorrenti.
Molti value types immutabili sono naturalmente sendable:
struct Prodotto: Sendable {
let id: String
let nome: String
let prezzoInCent: Int
}
Se tutte le proprietà sono Sendable, Swift può spesso aiutare con la conformità.
Perché serve
Immagina di passare un valore a un task concorrente:
Task {
await processa(prodotto)
}
Swift vuole sapere se quel valore può attraversare quel confine senza introdurre accessi concorrenti pericolosi.
Un value type immutabile è di solito sicuro. Una classe mutabile condivisa no.
Classe non Sendable
final class SessioneUtente {
var token: String
init(token: String) {
self.token = token
}
}
Questa classe è mutabile. Passarla tra task può essere rischioso.
Alternative:
- renderla immutabile;
- isolarla in un actor;
- proteggerla con sincronizzazione;
- evitare di condividerla.
Actor e Sendable
Gli actor sono pensati per accesso concorrente sicuro al loro stato isolato.
actor TokenStore {
private var token: String?
func get() -> String? {
token
}
func set(_ token: String?) {
self.token = token
}
}
Invece di condividere una classe mutabile, isoli lo stato.
@Sendable closure
Una closure @Sendable deve catturare valori in modo sicuro per la concurrency.
func esegui(_ operation: @Sendable @escaping () async -> Void) {
Task {
await operation()
}
}
Se la closure cattura stato mutabile non sicuro, Swift può avvisarti o bloccare la compilazione in modalità Swift 6.
Non usare unchecked alla leggera
Puoi vedere:
final class LegacyCache: @unchecked Sendable {
...
}
@unchecked Sendable dice al compilatore: fidati, ho garantito io la sicurezza.
Usalo solo se hai davvero sincronizzazione interna corretta. Non è un silenziatore da usare per far sparire errori.
Value type progettati bene
Questo è un buon candidato:
struct DettaglioProdotto: Sendable {
let prodotto: Prodotto
let recensioni: [Recensione]
}
Se Prodotto e Recensione sono Sendable, anche il dettaglio può esserlo.
Errori comuni
var cache: [String: Prodotto] = [:]
Task {
cache["p-1"] = prodotto
}
Stai catturando e modificando stato mutabile da un task. In Swift 6 questo tipo di situazione viene diagnosticato molto più severamente.
Meglio:
actor ProductCache {
private var storage: [String: Prodotto] = [:]
func set(_ prodotto: Prodotto, for id: String) {
storage[id] = prodotto
}
}
Sendable non significa thread-safe generico
Sendable non rende magicamente ogni operazione sicura. È un contratto sul trasferimento tra domini concorrenti.
Se hai stato mutabile condiviso, devi comunque usare isolamento: actor, MainActor, mutex o altri strumenti corretti.
Checklist
Per Swift 6 concurrency:
- preferisci value types immutabili per dati trasferiti;
- marca modelli di dominio come
Sendablequando ha senso; - evita classi mutabili condivise;
- usa actor per stato condiviso;
- tratta
@unchecked Sendablecome ultima risorsa; - rendi closure
@Sendablequando attraversano confini concorrenti; - non catturare variabili mutabili locali in task concorrenti.
Esercizio
Rendi Sendable:
Prodotto;Recensione;DettaglioProdotto;APIError.
Poi prova a passare una classe mutabile SessioneUtente dentro un Task e spiega perché è problematica.
Infine sostituiscila con un actor TokenStore.