Go è un linguaggio di programmazione compilato, staticamente tipizzato e con gestione automatica della memoria. È stato progettato per mantenere relativamente semplice il codice anche quando il software deve occuparsi di rete, concorrenza, servizi e sistemi che crescono nel tempo.
Se lo hai trovato cercando Golang, non stai parlando di un linguaggio diverso. Il nome ufficiale è Go; “Golang” è un soprannome nato dal vecchio dominio del progetto e rimasto molto diffuso come etichetta nelle ricerche e nelle community. La FAQ ufficiale del progetto Go chiarisce esplicitamente questa distinzione.
Il punto interessante, però, non è il nome. Go prova a risolvere un problema preciso: costruire software efficiente senza trasformare linguaggio, build e manutenzione in una quantità crescente di complessità accidentale.
Per questo lo trovi spesso in backend, API, servizi di rete, infrastruttura cloud, strumenti da riga di comando e software distribuito. Non significa che sia automaticamente la scelta migliore per questi progetti. Significa che il suo insieme di compromessi diventa particolarmente interessante quando semplicità operativa, concorrenza e manutenzione pesano quanto le prestazioni.
Cos’è Go e perché viene chiamato anche Golang
Go è un linguaggio general purpose: non nasce per un singolo tipo di applicazione e non è legato a un framework specifico.
La specifica ufficiale lo definisce come un linguaggio fortemente tipizzato, con garbage collection e supporto esplicito alla programmazione concorrente. I programmi sono organizzati in package, mentre il sistema di moduli gestisce versioni e dipendenze. La specifica del linguaggio Go descrive formalmente questi elementi.
Questa definizione tecnica dice che cosa possiede il linguaggio. Non spiega ancora perché esista.
Go o Golang: qual è il nome corretto
Il nome del linguaggio è Go.
La parola golang si è diffusa perché il sito del progetto utilizzava originariamente quel termine nel dominio. È rimasta comoda per distinguere il linguaggio dalla parola inglese “go”, estremamente generica, e ancora oggi viene usata nei motori di ricerca, negli hashtag e nei nomi di community.
Dal punto di vista editoriale conviene quindi mantenere entrambe le forme con funzioni differenti:
- Go quando parliamo correttamente del linguaggio;
- Golang quando serve disambiguare l’entità o intercettare il modo in cui viene cercata.
Scrivere continuamente “Golang” come se fosse il nome formale del progetto sarebbe impreciso. Ignorarlo completamente, invece, renderebbe più difficile collegare la terminologia ufficiale a quella che un lettore incontra normalmente online.
Perché Google ha progettato un nuovo linguaggio
Go nasce dall’insoddisfazione verso alcuni compromessi dei linguaggi e degli ambienti utilizzati per costruire software di grandi dimensioni.
La documentazione ufficiale racconta una tensione molto concreta: da una parte linguaggi capaci di offrire efficienza e controllo, dall’altra strumenti più fluidi e produttivi ma con compromessi diversi su tipi, esecuzione e prestazioni.
L’obiettivo non era creare “un C più facile” o “un Python più veloce”. Era trovare una combinazione differente fra:
- compilazione rapida;
- tipizzazione statica;
- esecuzione efficiente;
- garbage collection;
- concorrenza integrata nel linguaggio;
- dipendenze esplicite;
- sintassi e regole abbastanza contenute da rendere il codice leggibile da team differenti.
Questa origine aiuta a capire molte scelte che, viste singolarmente, possono sembrare limitazioni.
Go evita deliberatamente di accumulare alcune astrazioni presenti altrove. Quando il linguaggio rinuncia a una feature, il criterio non è necessariamente “non sarebbe utile”, ma anche quanto quella feature aumenterebbe la complessità del modello complessivo.
Open source, compatibilità e sviluppo del progetto
Go è un progetto open source sviluppato da un team in Google insieme a numerosi contributori della community e distribuito con licenza BSD-style. La pagina ufficiale del progetto Go mantiene i riferimenti a sorgenti, processo di sviluppo e risorse per i contributor.
Un elemento meno visibile, ma importante per chi deve mantenere software nel tempo, è la promessa di compatibilità della famiglia Go 1.
L’obiettivo è fare in modo che il normale codice scritto secondo la specifica continui a compilare e funzionare anche con release successive, salvo eccezioni documentate come problemi di sicurezza, dipendenze da comportamenti non specificati o uso di componenti particolarmente sensibili come unsafe.
Non è una promessa di compatibilità binaria universale: dopo un aggiornamento puoi dover ricompilare il programma. È soprattutto una promessa di stabilità a livello di sorgente e API fondamentali. Go 1 compatibility promise
Per una codebase destinata a vivere diversi anni, questo aspetto può pesare più della presenza dell’ennesima feature sintattica.
A cosa serve Go nella pratica
Go può essere usato per molti tipi di software, ma non ha senso descriverlo come un linguaggio “per fare qualsiasi cosa”. Se stai cercando di capire a cosa serve Golang, il modo più utile è partire dai problemi in cui il suo modello tecnico semplifica davvero sviluppo e manutenzione.
È più utile chiedersi in quali problemi le sue caratteristiche producono un vantaggio concreto.
Il terreno naturale è spesso quello in cui un programma deve comunicare attraverso la rete, gestire più attività contemporaneamente, essere distribuito con relativa semplicità e rimanere comprensibile mentre il progetto cresce.
Backend, API e servizi di rete
Lo sviluppo server-side è uno dei contesti in cui Go trova un incastro naturale. Non è un caso che Golang compaia spesso nelle discussioni su API, servizi di rete e backend che devono gestire molte operazioni concorrenti mantenendo il codice leggibile.
Un backend deve ricevere richieste, applicare logica, comunicare con database o servizi esterni, gestire errori e produrre risposte. Go offre nel linguaggio e nella libreria standard molti dei mattoni necessari per questo tipo di lavoro.
Per esempio, il package net/http permette di creare client e server HTTP senza dover installare immediatamente un framework esterno. La documentazione ufficiale espone direttamente handler, server, client, routing di base e primitive HTTP.
Questo non significa che un’applicazione reale non abbia bisogno di librerie o framework. Significa che puoi comprendere il percorso fondamentale prima di aggiungere ulteriori livelli di astrazione.
Lo stesso vale per le API: il linguaggio non sostituisce la progettazione del contratto HTTP, dell’autenticazione, degli errori o dei dati, ma rende relativamente diretto costruire servizi che espongono quelle operazioni.
Microservizi, cloud e sistemi distribuiti
Go è molto associato a infrastruttura cloud e sistemi distribuiti perché diverse caratteristiche si combinano bene con quel tipo di software:
- compilazione in eseguibili nativi con la toolchain standard;
- concorrenza integrata;
- librerie per rete e protocolli;
- avvio generalmente rapido;
- toolchain coerente;
- possibilità di mantenere servizi relativamente piccoli.
Il punto non è che “microservizi = Go”.
Un’architettura a microservizi introduce comunicazione di rete, retry, timeout, autenticazione tra servizi, osservabilità, deployment multipli e problemi di consistenza distribuita. Scegliere Go non elimina nessuna di queste responsabilità.
In molti casi un monolite ben strutturato rimane più semplice da costruire e gestire.
Go diventa interessante quando la distribuzione in più processi o servizi risponde già a un problema reale, non quando i microservizi vengono scelti soltanto perché il linguaggio li rende comodi da implementare.
CLI, automazione e strumenti DevOps
Un altro scenario molto naturale è la costruzione di programmi da riga di comando.
Puoi scrivere il programma, compilarlo e distribuire un eseguibile per la piattaforma di destinazione. Questo riduce spesso il numero di componenti che l’utente deve predisporre rispetto a strumenti che richiedono uno specifico interprete o ambiente applicativo.
Non bisogna però trasformare “Go produce un binario” in “il binario è sempre completamente autonomo”.
Configurazione della build, uso di cgo, librerie native e caratteristiche della piattaforma possono modificare le dipendenze operative del programma.
Il vantaggio reale è che la toolchain rende la compilazione e la distribuzione di molti programmi molto lineari, soprattutto per CLI, agent, utility di rete e tool di infrastruttura.
Dove Go non è la scelta più naturale
La versatilità non rende Go la scelta predefinita.
Se stai sviluppando l’interfaccia eseguita direttamente nel browser, JavaScript o TypeScript appartengono naturalmente a quel problema.
Se il progetto è centrato su data science, notebook, machine learning applicativo o un ecosistema scientifico già costruito attorno a Python, passare a Go può significare rinunciare a librerie e workflow che contano più del linguaggio stesso.
Se devi controllare memoria e risorse con maggiore precisione e vuoi evitare un garbage collector nel normale modello di esecuzione, Rust o C++ possono offrire compromessi più coerenti.
E se lavori dentro una grande piattaforma JVM con una codebase, librerie e competenze consolidate, introdurre Go soltanto perché appare più semplice può creare un secondo ecosistema da mantenere senza risolvere un problema concreto.
La domanda utile non è “cosa posso fare con Go?”, ma “quale problema mi permette di risolvere con meno complessità complessiva?”
Come funziona Go: compilazione, tipi e gestione della memoria
Per capire Go conviene separare almeno quattro elementi:
linguaggio → compilatore/toolchain → runtime → librerie

Mescolarli porta facilmente a conclusioni sbagliate.
La sintassi e il type system appartengono al linguaggio. go build appartiene alla toolchain. Garbage collector e scheduler delle goroutine sono responsabilità del runtime dell’implementazione standard. net/http, encoding/json, os e gli altri package appartengono alla libreria.
Dal codice sorgente al programma compilato
Con la toolchain standard, il normale percorso di un’applicazione Go è una compilazione ahead-of-time.
Scrivi file .go, il compilatore verifica e compila i package necessari e, quando il package principale è eseguibile, go build può produrre un file eseguibile per il target scelto.
Il runtime di Go non è una macchina virtuale analoga alla JVM. La documentazione ufficiale distingue esplicitamente le due cose: la libreria runtime fornisce servizi fondamentali come garbage collection, gestione degli stack e concorrenza, mentre il normale programma viene compilato in codice macchina.
Il comando di build segue una logica semplice:
go build
La documentazione del comando specifica che go build compila i package richiesti insieme alle dipendenze; per un main package produce normalmente l’eseguibile risultante. Documentazione di go build
Questa catena relativamente corta è uno dei motivi per cui Go risulta comodo nei progetti in cui build e deployment devono essere facili da riprodurre.
Tipizzazione statica e garbage collection
Go è staticamente tipizzato.
Il compilatore conosce i tipi e può intercettare diverse incompatibilità prima che il programma venga eseguito. Questo non significa che il type system impedisca ogni errore né che il codice staticamente tipizzato sia automaticamente più sicuro.
Significa che una parte importante dei contratti del programma viene verificata durante la compilazione.
La memoria segue invece un modello gestito.
Con la toolchain standard, il runtime include un garbage collector che recupera memoria non più necessaria. Il programmatore non esegue normalmente un free manuale per ogni oggetto allocato.
La guida ufficiale al garbage collector di Go distingue anche allocazioni che possono rimanere legate allo stack da valori che devono invece “scappare” verso l’heap.
Il vantaggio è ridurre una parte della gestione manuale della memoria.
Il costo è che il garbage collector esiste, utilizza risorse e può diventare rilevante in software particolarmente sensibile a latenza, allocazioni o footprint.
Dire “Go gestisce la memoria da solo” è quindi vero solo a un livello superficiale. Se le prestazioni contano davvero, devi comunque capire quanto allochi, dove allochi e quanto lavoro stai imponendo al runtime.
Package, moduli e toolchain
Un programma Go viene organizzato in package.
Un package raccoglie file sorgente della stessa directory compilati insieme. Più package correlati possono appartenere a un modulo, che definisce un’unità di versionamento e distribuzione.
Il file centrale è go.mod.
Un progetto può iniziare con:
mkdir esempio-go cd esempio-go go mod init example.com/esempio-go
Il comando crea il file go.mod, nel quale vengono dichiarati il percorso del modulo e le informazioni necessarie a gestire le dipendenze.
La Go Modules Reference definisce i moduli come il meccanismo standard con cui Go gestisce le dipendenze.
Questa integrazione è importante perché riduce il numero di decisioni preliminari.
Nel normale workflow incontri rapidamente comandi come:
go mod init go mod tidy go test ./... go run . go build go fmt go vet
Non significa che l’ecosistema non abbia altri strumenti. Significa che molti task di base condividono una toolchain ufficiale e convenzioni comuni.
Libreria standard: perché conta più di quanto sembra
La libreria standard di Go è una parte sostanziale del valore del linguaggio.
Se devi manipolare file, lavorare con JSON, fare richieste HTTP, creare server, gestire crittografia, testare codice, utilizzare template o dialogare con il sistema operativo, trovi già molti componenti nella distribuzione standard.
Questo cambia il modo in cui valuti l’ecosistema.
Il numero totale di package di terze parti è interessante, ma non racconta quante dipendenze servano realmente per completare un progetto.
Un linguaggio con una libreria standard ampia può permetterti di rimandare l’introduzione di framework e dipendenze finché il problema non li rende utili.
Per un piccolo servizio HTTP, per esempio, puoi partire direttamente da net/http e capire request, response e handler prima di decidere se un router o un framework aggiunge valore.
Goroutine e channel: come funziona la concorrenza in Go

La concorrenza è uno degli elementi più caratteristici di Go, ma anche uno dei più facilmente trasformati in slogan. Quando si parla di Golang, goroutine e channel sono spesso il primo elemento citato, ma il loro valore dipende da come vengono usati nel modello complessivo del programma.
La sintassi rende semplice avviare lavoro concorrente. Questo non rende automaticamente semplice progettare software concorrente corretto.
Che cos’è una goroutine
Una goroutine è una funzione eseguita come flusso concorrente gestito dal runtime Go.
Se hai una funzione:
func elabora() {
// lavoro
}
puoi avviarla come goroutine aggiungendo go:
go elabora()
La specifica stabilisce che una go statement avvia la chiamata della funzione indipendentemente dal flusso che l’ha creata.
Non equivale a dire che ogni goroutine crea un nuovo thread del sistema operativo.
Il runtime multiplexa le goroutine sui thread disponibili e gestisce scheduling, stack e altre operazioni necessarie. La FAQ ufficiale spiega proprio questa differenza e il motivo per cui il modello permette di creare un numero elevato di attività concorrenti con un overhead normalmente inferiore a quello di un modello “un thread di sistema per attività”.
Questo rende le goroutine molto comode per server, pipeline, I/O concorrente e operazioni indipendenti.
Rimane però necessario controllarne il ciclo di vita.
Una goroutine che non termina quando dovrebbe può trattenere memoria, connessioni o altre risorse esattamente come qualsiasi altro lavoro lasciato in esecuzione.
A cosa servono i channel
I channel permettono alle goroutine di inviare e ricevere valori.
Un esempio minimale:
package main
import "fmt"
func quadrato(n int, out chan<- int) {
out <- n * n
}
func main() {
risultati := make(chan int, 3)
for _, n := range []int{2, 3, 4} {
go quadrato(n, risultati)
}
somma := 0
for i := 0; i < 3; i++ {
somma += <-risultati
}
fmt.Println(somma)
}
Le tre chiamate a quadrato possono procedere concorrentemente e comunicano il risultato attraverso il channel.
Il channel in questo esempio è bufferizzato. I channel possono anche essere senza buffer: in quel caso invio e ricezione introducono un punto di sincronizzazione fra le goroutine coinvolte.
La documentazione ufficiale descrive i channel come primitive per comunicazione e sincronizzazione, ma questo non significa che ogni stato condiviso debba essere modellato con un channel.
Mutex, atomiche e altre primitive di sync rimangono appropriate in molti casi.
Il criterio dovrebbe partire dal modello del problema, non dal desiderio di utilizzare la caratteristica più riconoscibile del linguaggio.
Concorrenza e parallelismo non sono la stessa cosa
Due attività sono concorrenti quando il sistema deve gestire il loro avanzamento nello stesso intervallo di tempo.
Sono parallele quando vengono eseguite letteralmente nello stesso momento su risorse di calcolo differenti.
Puoi quindi progettare un programma concorrente anche se, in un determinato istante, una sola operazione sta utilizzando la CPU.
Questa distinzione evita un errore frequente: pensare che aggiungere goroutine equivalga automaticamente a utilizzare più core e ridurre il tempo di esecuzione.
Se il problema è dominato da I/O, la concorrenza può evitare di lasciare inutilizzato il programma mentre una richiesta attende rete, disco o altri servizi.
Se il problema è CPU-bound, invece, il risultato dipende da parallelismo disponibile, quantità di lavoro, sincronizzazione, contesa, allocazioni e algoritmo.
Perché più goroutine non significano automaticamente più performance
Una goroutine ha un costo contenuto, ma non nullo.
Crearne troppe senza controllo può generare:
- più scheduling;
- contesa;
- memoria trattenuta;
- pressione sul garbage collector;
- code interne;
- sincronizzazioni;
- errori più difficili da riprodurre.
Immagina di dover processare un milione di record.
La soluzione ingenua potrebbe essere:
for _, record := range records {
go process(record)
}
Ma non hai ancora stabilito quante operazioni simultanee il database, la rete o il servizio esterno possano realmente sostenere.
Un worker pool limitato può essere molto più efficace perché rende esplicita la capacità del sistema.
Concorrenza è una tecnica per organizzare il lavoro. Le prestazioni sono un risultato da misurare.
Confondere le due cose porta facilmente a programmi più complessi e non necessariamente più veloci.
I vantaggi di Go che incidono davvero su un progetto
Le liste di vantaggi di Go tendono a ripetere “veloce, semplice, scalabile”. Per valutare Golang in modo utile, però, queste etichette vanno ricondotte a meccanismi concreti: sintassi contenuta, toolchain integrata, runtime e libreria standard.
Sono parole troppo generiche se non spieghiamo da dove deriva il vantaggio.
Un linguaggio volutamente piccolo e leggibile
Go ha relativamente pochi costrutti e una sintassi deliberatamente contenuta.
Questo limita alcuni modi possibili di esprimere la stessa operazione e favorisce una certa uniformità fra codebase differenti.
È particolarmente visibile con gofmt, che standardizza automaticamente la formattazione. Molte discussioni che in altri ecosistemi diventano convenzioni interne al team vengono così eliminate alla fonte.
Il vantaggio non è scrivere necessariamente meno caratteri.
È ridurre il numero di decisioni che ogni sviluppatore deve interpretare quando entra in una codebase.
Questo può pesare molto nei team e nei progetti mantenuti a lungo.
Il rovescio della medaglia è che uno sviluppatore abituato a type system o costrutti più espressivi può percepire Go come deliberatamente restrittivo.
Ed è una percezione corretta: parte della semplicità nasce proprio da ciò che il linguaggio decide di non rendere troppo sofisticato.
Tooling integrato e convenzioni condivise
Build, formattazione, test, gestione dei moduli, analisi e documentazione partono da strumenti con convenzioni condivise.
Questo riduce la frammentazione iniziale del progetto.
Puoi certamente aggiungere Makefile, container, CI, generatori, linter e altri strumenti. Ma il progetto non parte necessariamente da una pagina bianca in cui ogni team deve inventare il proprio ciclo elementare di sviluppo.
Il vantaggio emerge soprattutto quando più repository devono seguire pratiche simili.
Non devi ricordare un comando di build completamente diverso per ogni progetto soltanto perché qualcuno ha scelto un tool differente.
Build e distribuzione relativamente semplici
Per un’applicazione main, go build può produrre direttamente un eseguibile.
In molti contesti questo semplifica il deployment: distribuisci l’artefatto appropriato e la configurazione richiesta dal programma, invece di ricostruire sull’host l’intero ambiente di sviluppo.
Non è corretto trasformarlo nella promessa “un unico binario senza dipendenze in ogni situazione”.
cgo, librerie dinamiche, certificati, dati esterni e configurazioni possono cambiare il quadro.
La caratteristica utile è un’altra: per molti programmi Go, il percorso sorgente → build → artefatto distribuibile rimane molto leggibile.
Quando semplicità e concorrenza aiutano anche il team
Il valore di Go non riguarda soltanto la macchina che esegue il codice.
In un servizio che deve essere mantenuto da più persone, contano anche:
- tempo necessario per capire il progetto;
- uniformità del codice;
- facilità nel riprodurre la build;
- chiarezza delle dipendenze;
- costo del debugging;
- quantità di conoscenza specifica del framework necessaria per intervenire.
Go può avere senso quando vuoi mantenere contenuta questa superficie.
Non elimina la complessità del dominio.
Un sistema di pagamenti rimane difficile. Un database distribuito rimane difficile. Un protocollo di consenso rimane difficile.
Il vantaggio possibile è evitare di aggiungere complessità linguistica e di tooling a quella che il problema possiede già.
I limiti di Go e i compromessi da conoscere
Se una guida presenta Go come veloce, semplice e perfetto per il cloud senza spiegare che cosa perdi in cambio, non sta aiutando a scegliere.
Ogni decisione del linguaggio sposta complessità da una parte all’altra.
Error handling esplicito e codice più verboso
Go utilizza comunemente valori error restituiti dalle funzioni.
Un pattern tipico è:
result, err := operazione()
if err != nil {
return err
}
Questo rende il percorso dell’errore molto visibile.
È un vantaggio quando vuoi capire dove un errore viene controllato, trasformato o propagato.
Può però produrre ripetizione, soprattutto in funzioni che concatenano numerose operazioni fallibili.
Il linguaggio non adotta il normale modello try/catch per gli errori ordinari e questa è una scelta progettuale intenzionale.
Il risultato è un compromesso: meno controllo implicito, più gestione esplicita nel codice.
Per alcuni team è chiarezza. Per altri è verbosità. Entrambe le letture possono essere ragionevoli.
Garbage collector e controllo delle risorse
Il garbage collector elimina gran parte della gestione manuale della memoria, ma non rende il costo della memoria irrilevante.
Un programma può:
- allocare troppo;
- mantenere riferimenti più a lungo del necessario;
- creare pressione sul GC;
- costruire strutture dati inefficienti;
- copiare dati inutilmente.
Se il tuo software richiede controllo estremamente fine delle allocazioni o latenze in cui anche le pause e il lavoro del garbage collector diventano determinanti, un modello differente può essere più appropriato.
È uno dei motivi per cui il confronto con Rust non può ridursi a “quale linguaggio è più veloce”.
I due linguaggi spostano responsabilità differenti sul compilatore, sul runtime e sul programmatore.
Un linguaggio meno espressivo per scelta
Go evita intenzionalmente alcune costruzioni o livelli di astrazione che altri linguaggi rendono centrali.
Questo non significa che il linguaggio sia rimasto immobile.
Per esempio, Go supporta i generics tramite type parameters. Le guide che indicano ancora “assenza dei generics” come limite del linguaggio descrivono una situazione superata. La FAQ ufficiale documenta il supporto attuale e il ragionamento progettuale che ha portato a introdurlo senza abbandonare la ricerca di semplicità.
Rimane comunque un linguaggio che tende a privilegiare pochi meccanismi relativamente espliciti rispetto a una continua espansione delle possibilità sintattiche.
Se il team apprezza type system molto sofisticati, programmazione funzionale avanzata o metaprogrammazione estesa, Go può sembrare meno espressivo di altre alternative.
È parte del prodotto, non un incidente.
Ecosistema e dominio applicativo contano più della popolarità
Un linguaggio non viene scelto nel vuoto.
Devi guardare almeno:
- librerie disponibili;
- framework;
- strumenti di osservabilità;
- driver;
- SDK dei servizi che utilizzi;
- competenze del team;
- ambiente di deployment;
- codice esistente;
- requisiti non funzionali.
Go possiede un ecosistema molto forte nel backend, networking, cloud e tooling.
Questo non implica che abbia la migliore libreria per qualsiasi dominio.
Se devi utilizzare un framework, un SDK o un ambiente molto più maturo in un altro ecosistema, quella differenza può superare qualsiasi preferenza per la sintassi di Go.
Go vs Python, Java e Rust: quando cambia la scelta
Il confronto fra linguaggi ha senso solo se specifichi che cosa stai cercando di ottimizzare.
“Qual è il migliore?” non ha una risposta tecnica utile.
Questa tabella è quindi una decision matrix qualitativa, non una classifica di prestazioni:
| Criterio | Go | Python | Java | Rust |
|---|---|---|---|---|
| Modello dei tipi | Statico | Dinamico | Statico | Statico |
| Gestione memoria tipica | Garbage collector | Runtime gestito | Garbage collector nella JVM | Ownership/borrowing, senza GC come modello normale |
| Build/esecuzione | Compilazione nativa con toolchain standard | Interprete/runtime, a seconda dell’implementazione | Bytecode su JVM | Compilazione nativa |
| Concorrenza | Goroutine, channel e primitive sync | Dipende molto da runtime e workload | Thread, executor, virtual thread e librerie JVM | Thread, async ecosystem e primitive safe |
| Punto di forza tipico | Backend, rete, cloud, CLI, servizi | Automazione, data, ML, scripting, backend | Enterprise, JVM, grandi ecosistemi applicativi | Sistemi, controllo risorse, native performance |
| Complessità iniziale | Contenuta | Molto accessibile per iniziare | Più ecosistema/runtime da comprendere | Curva più ripida |
| Controllo low-level | Medio | Basso | Basso/medio | Alto |
La tabella indica il baricentro, non i limiti assoluti. Tutti questi linguaggi possono uscire dai rispettivi casi d’uso tipici.
Go vs Python: produttività, ecosistema e deployment
Se confronti Go con Python, la prima differenza utile non è “compilato contro interpretato”.
È il compromesso complessivo.
Python tende a offrire un ciclo iniziale estremamente rapido e un ecosistema enorme in automazione, data analysis, machine learning, scripting e sviluppo applicativo.
Go offre tipizzazione statica, compilazione e un modello di concorrenza molto più integrato nel linguaggio e nel runtime.
Per uno script di automazione di poche decine di righe, Python può permetterti di arrivare prima al risultato.
Per un servizio di rete che vuoi compilare, distribuire come artefatto e mantenere con un modello di tipi statico, Go può risultare più lineare.
Non c’è contraddizione: sono ottimizzazioni differenti.
Go vs Java: semplicità della toolchain ed ecosistema enterprise
Il confronto con Java è più vicino perché entrambi possono essere usati con successo in backend e sistemi di grandi dimensioni.
Java porta con sé l’ecosistema JVM, decenni di librerie, framework enterprise, tooling molto maturo e un modello applicativo utilizzato in enormi codebase.
Go tende ad avere un linguaggio e una toolchain più piccoli.
Anche il modello di distribuzione normale è differente:
Go → sorgente → codice nativo
Java → sorgente → bytecode → JVM
Questo non rende Go automaticamente più semplice in ogni progetto.
Se l’organizzazione possiede già servizi, librerie, osservabilità, build pipeline e competenze JVM, migrare o introdurre Go crea anche un nuovo stack operativo.
La semplicità va misurata a livello di sistema e team, non soltanto contando i concetti del linguaggio.
Go vs Rust: garbage collector, memory safety e controllo
Il confronto Go vs Rust è forse il più interessante quando prestazioni e infrastruttura entrano nella discussione, ma i due linguaggi risolvono il problema della memoria in modi molto differenti. Un confronto Go vs Rust utile parte quindi dal modello di memoria e dal livello di controllo richiesto, non da una classifica generica di velocità.
Il linguaggio Rust utilizza ownership e borrowing per imporre molte garanzie attraverso il compilatore e non basa il normale modello di gestione della memoria su un garbage collector.
Go accetta invece un runtime con garbage collection per alleggerire il lavoro quotidiano dello sviluppatore.
In termini molto semplificati:
Go sposta più responsabilità di memoria verso il runtime.
Rust sposta più responsabilità verso il compilatore e il modello del linguaggio.
Il risultato è che Go può risultare più rapido da apprendere e più leggero da introdurre in molti servizi, mentre Rust offre controllo e garanzie particolarmente interessanti quando memoria, risorse e comportamento low-level sono centrali.
Non è necessario trasformare questa differenza in una guerra di benchmark.
Per un normale servizio CRUD, la latenza del database potrebbe contare enormemente più del linguaggio.
Per una componente infrastrutturale performance-critical, invece, il modello di memoria può diventare una decisione architetturale.
La decision matrix: quale compromesso serve al tuo progetto
Una scelta pratica può partire così.
Sceglierei Go se il problema centrale è costruire servizi, API, tooling o software di rete con tipizzazione statica, concorrenza accessibile e una toolchain relativamente uniforme.
Sceglierei Python se il valore principale viene dalla velocità con cui posso sviluppare, dallo scripting o da ecosistemi come data science e machine learning.
Sceglierei Java se il progetto beneficia fortemente della JVM, di framework enterprise consolidati o di una piattaforma organizzativa già costruita attorno a quell’ecosistema.
Sceglierei Rust se controllo della memoria, prevedibilità delle risorse e software nativo sono requisiti sufficientemente importanti da giustificare una curva di apprendimento maggiore.
La variabile decisiva rimane il software che devi costruire e mantenere.
Come iniziare a programmare con Go senza partire da un framework
Se il tuo obiettivo è programmare con Go, partire immediatamente da Gin, Echo o da uno stack complesso può farti imparare soprattutto il framework. Per programmare con Go con basi solide, conviene prima capire modulo, package, build e ciclo di una richiesta HTTP senza nasconderli dietro astrazioni esterne.
È più utile vedere almeno una volta l’intera catena fondamentale:
installazione → modulo → sorgente → esecuzione → build → HTTP
Installare una release stabile e verificare la toolchain
Il punto di partenza più sicuro è la procedura ufficiale di installazione di Go.
Dopo l’installazione verifica che il comando sia disponibile:
go version
Poi crea una directory per il progetto:
mkdir primo-go cd primo-go
e inizializza un modulo:
go mod init example.com/primo-go
Il nome example.com/primo-go va bene per un esercizio locale. Un modulo destinato alla pubblicazione dovrebbe invece utilizzare un module path coerente con il luogo da cui il codice sarà recuperabile.
Dal primo file a go run e go build
Crea main.go:
package main
import "fmt"
func main() {
fmt.Println("Ciao, Go!")
}
Eseguilo direttamente:
go run .
go run è comodo durante lo sviluppo perché compila ed esegue il programma in un unico passaggio operativo.
Quando vuoi produrre l’eseguibile:
go build
Per un progetto iniziale questi pochi passaggi mostrano già diversi elementi importanti:
package mainidentifica un programma eseguibile;importdichiara i package usati;func main()è il punto di ingresso;- il modulo descrive il progetto e le sue dipendenze;
- la toolchain compila il codice prima dell’esecuzione.
La guida introduttiva ufficiale segue lo stesso percorso e introduce progressivamente comandi, package e moduli.
Un piccolo servizio HTTP prima di Gin o altri framework
A questo punto puoi già costruire un server HTTP senza dipendenze esterne:
package main
import (
"fmt"
"log"
"net/http"
)
func saluta(nome string) string {
return fmt.Sprintf("Ciao, %s!", nome)
}
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, saluta("Go"))
})
log.Println("server in ascolto")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Eseguilo:
go run .
Poi apri:
http://localhost:8080/hello
e il server restituisce:
Ciao, Go!
Questo esempio è volutamente piccolo.
Non contiene routing avanzato, middleware, autenticazione, database, logging strutturato, graceful shutdown o configurazione da produzione.
Ed è proprio il motivo per cui è utile: mostra che cosa sta facendo Go prima che un framework inizi a prendere decisioni al posto tuo.
Quando sai seguire una richiesta dal ListenAndServe fino all’handler e alla risposta, puoi valutare molto meglio che cosa ti offre un framework.
A quel punto ha senso passare a un tutorial dedicato che affronti progressivamente strutture dati, funzioni, interfacce, error handling, test, moduli, concorrenza e un progetto più completo.
Conclusione
Go ha senso soprattutto quando vuoi ridurre la complessità necessaria per costruire e mantenere software server-side, di rete o infrastrutturale, senza rinunciare a tipizzazione statica, compilazione e concorrenza integrata.
Le goroutine sono utili, ma non rendono automaticamente un programma veloce. Il garbage collector semplifica la memoria, ma non ne elimina il costo. La compilazione facilita molti deployment, ma non rende ogni eseguibile completamente indipendente dal proprio ambiente.
Sono proprio queste qualificazioni a rendere Go interessante.
Il linguaggio accetta alcuni limiti di espressività e alcune responsabilità del runtime in cambio di un modello relativamente piccolo, una toolchain coerente e un percorso abbastanza diretto dal sorgente al software in esecuzione.
Lo sceglierei per API, backend, servizi di rete, CLI e tooling quando questi compromessi riducono davvero il costo del progetto.
Lo sceglierei con più cautela quando l’ecosistema dominante del problema è altrove, quando serve controllo molto più fine delle risorse o quando introdurlo significherebbe soltanto aggiungere un nuovo stack a un sistema che funziona già bene.
Il prossimo passo, quindi, non è installare il framework Go più popolare. Se stai valutando Golang per un progetto reale, è più utile scrivere qualche programma piccolo, compilare un servizio HTTP, usare package e moduli e capire bene il modello di concorrenza.
Dopo questo passaggio diventa molto più semplice decidere se Go è soltanto un linguaggio interessante da conoscere oppure quello giusto per il software che vuoi costruire.