Cos'è Dart e perché esiste solo grazie a Flutter
Cos'è Dart: la doppia compilazione JIT e AOT che rende possibile l'hot reload, il modello a isolate, la null safety e i limiti fuori da Flutter.
Hai deciso di provare Flutter, hai scoperto che si scrive in un linguaggio di cui non avevi mai sentito parlare, e la prima domanda ragionevole è: perché dovrei imparare Dart invece di uno dei linguaggi che tutti usano? In questo articolo trovi la risposta onesta — che comincia con un "probabilmente non dovresti, se non fai Flutter" — e poi il motivo tecnico serio per cui Google ha scelto proprio lui.
Cos'è Dart
Dart è un linguaggio creato da Google nel 2011, oggi usato quasi esclusivamente come linguaggio di Flutter, la cui caratteristica distintiva è poter essere compilato in due modi completamente diversi: al volo durante lo sviluppo e in binario nativo per la produzione.
Partiamo dalla parte scomoda, perché è quella che serve davvero per decidere.
Dart era nato con un altro obiettivo: sostituire JavaScript nel browser. Google voleva metterci dentro una macchina virtuale Dart nativa in Chrome. Non è successo, il resto dell'industria non ha seguito, e il progetto è rimasto senza un motivo per esistere. È stato Flutter, anni dopo, a dargliene uno.
Quindi la risposta diretta alla domanda che ti stai facendo: se non hai intenzione di usare Flutter, imparare Dart non ha praticamente senso. Non c'è un ecosistema backend rilevante, non c'è una nicchia di data science, non c'è un mercato del lavoro per "sviluppatore Dart" separato da "sviluppatore Flutter". Le offerte dicono Flutter, non Dart.
Detto questo: se Flutter ti interessa, il linguaggio che lo accompagna è ben fatto e c'è una ragione tecnica precisa dietro la scelta.
La doppia compilazione: perché Flutter è piacevole da usare
Questo è il punto che giustifica Dart, ed è meno noto di quanto meriti.
Un linguaggio, in genere, sta da una parte o dall'altra. Interpretati o compilati al volo — Python, JavaScript — sono flessibili e veloci da modificare, ma pagano in prestazioni e non producono un binario autonomo. Compilati in anticipo — C++, Rust, Go — sono veloci a runtime, ma ogni modifica richiede una ricompilazione e un riavvio.
Dart fa entrambe le cose, e in modo ufficialmente supportato:
- In sviluppo usa la compilazione JIT (just-in-time). Il codice viene compilato mentre gira, quindi può essere sostituito a caldo.
- In produzione usa la compilazione AOT (ahead-of-time). Ottieni un binario nativo per ARM o x86, senza interprete, senza macchina virtuale al seguito.
La conseguenza pratica della prima è l'hot reload: modifichi una riga, salvi, e in meno di un secondo l'app sullo schermo si aggiorna mantenendo lo stato. Sei dentro la quarta schermata di un flusso di registrazione, cambi il colore di un bottone, e lo vedi cambiare senza dover rifare tutto il percorso.
Text(
'Ciao',
style: TextStyle(fontSize: 24, color: Colors.blue),
// cambi 24 in 32, salvi, lo vedi subito: niente riavvio
)
Detta così sembra una comodità minore. Nella pratica cambia il ritmo del lavoro: costruire un'interfaccia diventa un ciclo da pochi secondi invece che da minuti, e questo è, molto più della sintassi, il motivo per cui chi prova Flutter tende a trovarlo piacevole.
La conseguenza della seconda è che l'app rilasciata non porta con sé un interprete. È codice macchina, parte veloce e non ha le pause di avvio tipiche degli ambienti con macchina virtuale.
Nessun altro linguaggio mainstream offre questa combinazione come modalità di lavoro normale, ed è il motivo concreto per cui Flutter non è stato costruito su un linguaggio già affermato.
Il modello a isolate: niente memoria condivisa
Il secondo aspetto tecnico interessante riguarda la concorrenza.
In quasi tutti i linguaggi con thread, più esecuzioni parallele accedono alla stessa memoria. Da lì nascono le race condition: due thread che scrivono lo stesso dato, un risultato che dipende dall'ordine, bug che si manifestano una volta su mille e non si riproducono mai quando li cerchi. È il problema che Rust affronta con il compilatore e che Go attenua con i canali.
Dart lo elimina per costruzione: un isolate ha la propria memoria e nessun altro può toccarla. Gli isolate comunicano solo scambiandosi messaggi.
// Elabora qualcosa di pesante senza bloccare l'interfaccia
final risultato = await Isolate.run(() => calcoloCostoso(dati));
Niente lock, niente mutex, niente sezioni critiche. Se due isolate non condividono nulla, non possono corrompere nulla a vicenda.
Il prezzo c'è ed è onesto: passare dati tra isolate significa copiarli, e su volumi grandi la copia costa. Non è il modello giusto se devi condividere di continuo strutture enormi.
Va chiarito un punto che confonde sempre: il codice Dart di un'app Flutter gira normalmente su un isolate solo, e l'asincronia quotidiana si fa con async/await su un event loop, esattamente come in JavaScript.
Future<Profilo> caricaProfilo(String id) async {
final utente = await api.getUtente(id);
final ordini = await api.getOrdini(utente.id);
return Profilo(utente, ordini);
}
Questo non è parallelismo: è attesa non bloccante. Gli isolate servono quando devi fare calcolo vero — decodificare immagini, parsare un JSON enorme, comprimere — e non vuoi che l'interfaccia scatti.
Sound null safety
Come Kotlin e Swift, Dart mette i valori nulli nel sistema dei tipi:
String nome = 'Edoardo';
nome = null; // errore di compilazione
String? soprannome; // questo può essere nullo
print(soprannome?.length); // null-aware
print(soprannome ?? 'anonimo'); // valore di riserva
L'aggettivo sound ha un significato tecnico: la garanzia è così forte che il compilatore può usarla per ottimizzare. Se un tipo non è nullabile, non ci sarà mai un valore nullo lì dentro, e il codice generato può saltare i controlli. Non è solo un aiuto per te che scrivi, produce anche un binario migliore.
Come altrove, esiste la scorciatoia ! per dire "fidati, non è nullo". E come altrove, usarla per zittire il compilatore significa riportare a runtime i crash che il sistema dei tipi stava evitando.
Come si legge il codice Dart
Se conosci un linguaggio a parentesi graffe non hai sorprese. La sintassi è deliberatamente familiare: classi, interfacce, generici, tipizzazione statica con inferenza.
class Utente {
final String nome;
final int eta;
const Utente({required this.nome, required this.eta});
bool get maggiorenne => eta >= 18;
}
void main() {
final utenti = [
Utente(nome: 'Anna', eta: 30),
Utente(nome: 'Luca', eta: 15),
];
final adulti = utenti.where((u) => u.maggiorenne).map((u) => u.nome).toList();
print(adulti); // [Anna]
}
I parametri con nome (nome:, eta:) sono una scelta di progetto che si sente parecchio in Flutter, dove costruisci alberi di widget con molti parametri: leggere Utente(nome: 'Anna', eta: 30) è meno ambiguo di Utente('Anna', 30).
Chi arriva da TypeScript o Java è produttivo in pochi giorni. Dart non chiede riprogrammazione mentale: le cose da imparare sono Flutter e i suoi modelli di gestione dello stato, non il linguaggio.
I limiti, senza addolcirli
L'ecosistema di pacchetti è molto più piccolo. Il registro ufficiale è pub.dev. Per le esigenze comuni di un'app mobile trovi quasi sempre quello che serve. Per qualcosa di specifico — un SDK di un fornitore di nicchia, una libreria scientifica, un formato particolare — la probabilità di non trovare nulla, o di trovare un pacchetto abbandonato da due anni con tre issue aperte, è concreta. Confrontalo mentalmente con npm o PyPI e capisci la differenza di ordine di grandezza.
Fuori da Flutter, Dart non ti serve quasi a niente. Esistono framework backend in Dart e si possono scrivere strumenti a riga di comando. Funzionano, ma l'adozione è marginale: nessuno assume per quello, e sceglierlo per un backend significa rinunciare alle librerie mature di Node.js, Python, Java o Go.
È una competenza legata a una tecnologia sola. Se un giorno lasci Flutter, ciò che ti resta sono i concetti — programmazione a oggetti, asincronia, null safety — non il linguaggio. Questo lo rende un investimento più fragile rispetto a linguaggi trasversali.
L'app Flutter non parla la lingua della piattaforma senza aiuto. Per accedere a funzionalità native che il framework non copre servono i platform channel e, spesso, un po' di codice Kotlin o Swift dall'altra parte. La promessa di scrivere una volta sola ha dei confini.
Se stai ancora decidendo l'approccio, i confronti utili sono in React Native o Flutter e in scegliere la tecnologia per un'app mobile.
Errori comuni
Fare calcolo pesante dentro async e aspettarsi che non blocchi. async/await non sposta il lavoro altrove: se la funzione fa un ciclo da dieci milioni di iterazioni, l'interfaccia si congela lo stesso. Per il calcolo vero serve un isolate.
Usare ! sui valori che arrivano dalla rete. Il JSON è la fonte meno affidabile che esista. Un campo che cambia lato server e l'app si chiude.
Confondere final e const. final significa "assegnato una volta a runtime", const significa "noto già a compilazione". In Flutter mettere const dove possibile evita che i widget vengano ricostruiti inutilmente, e ha un effetto reale sulle prestazioni.
Dimenticare di rilasciare le risorse. Controller, stream, listener: se non li chiudi nel dispose(), restano vivi. Su una lista di schermate aperte e chiuse è una perdita di memoria che si accumula in silenzio.
Scegliere Flutter e poi trattare Dart come un dettaglio da ignorare. Quasi tutti i problemi di prestazioni e di stato in Flutter si spiegano con il comportamento del linguaggio: cosa viene ricostruito, cosa è immutabile, quando l'event loop è occupato.
Aggiungere un pacchetto per ogni cosa senza guardarne lo stato. Con un ecosistema piccolo, la qualità media è più variabile. Controlla ultima pubblicazione, issue aperte e se supporta le versioni recenti prima di legarci il progetto.
In sintesi
Dart è il linguaggio di Flutter, e sostanzialmente niente altro. Va detto subito perché è la cosa che serve per decidere: non esiste un percorso professionale in Dart che non passi da Flutter, e impararlo fuori da quel contesto è tempo speso male.
La giustificazione tecnica è la doppia compilazione. JIT in sviluppo, e da lì l'hot reload che mantiene lo stato e rende il lavoro sull'interfaccia un ciclo da secondi; AOT in produzione, e da lì un binario nativo senza interprete al seguito. Nessun altro linguaggio mainstream offre entrambe come modalità normali di lavoro.
Il modello a isolate elimina le race condition classiche perché elimina la memoria condivisa: si comunica per messaggi, si paga in copie dei dati. Ma nell'uso quotidiano l'asincronia è async/await su un event loop, e gli isolate servono solo per il calcolo pesante.
I limiti sono l'ecosistema e la trasferibilità. Il registro dei pacchetti è molto più piccolo di npm o PyPI, e se un giorno lasci Flutter il linguaggio non ti resta in mano. È una scommessa su una tecnologia, non un investimento neutro come Python o JavaScript. Per valutarla nel quadro completo c'è tutti i linguaggi di programmazione, e per capire cosa rende quel percorso quanto guadagna un mobile developer in Italia.
Se decidi che Flutter è la strada, un percorso guidato ti evita di imparare Dart e il framework a spizzichi: trovi tutto nei corsi.