Salut ! Aujourd'hui, on va explorer Go (aussi appelé Golang), le langage créé par Google qui a révolutionné le développement backend. Si tu as déjà galéré avec la complexité de C++, la lenteur de Python, ou les dépendances cauchemardesques de Node.js, Go va te faire respirer un grand bol d'air frais.
Go, c'est le langage qui allie la simplicité de Python, la performance de C, et la facilité de déploiement d'un binaire statique. Pas de JVM, pas d'interpréteur, pas de dépendances à installer - juste un exécutable qui marche partout.
Dans cet article, je vais t'expliquer pourquoi Go est devenu si populaire, et te guider à travers tous ses concepts fondamentaux. À la fin, tu seras capable de créer des applications Go performantes et concurrentes.
Prêt à découvrir Go ? Let's go ! 🚀
Pourquoi Go ?
Avant de plonger dans le code, comprenons pourquoi Go a été créé et pourquoi il connaît un tel succès.
L'histoire de Go
Go a été créé en 2009 chez Google par Robert Griesemer, Rob Pike et Ken Thompson (oui, LE Ken Thompson, co-créateur d'Unix !). Ils en avaient marre des temps de compilation interminables en C++ et voulaient un langage :
-
Simple : Peu de concepts à apprendre
-
Rapide : Compilation ultra-rapide, exécution performante
-
Moderne : Support natif de la concurrence
-
Productif : Déploiement facile, outillage excellent
Pourquoi les développeurs adorent Go
Performance proche du C : Go compile en code machine natif. Pas d'interpréteur, pas de VM. Le résultat ? Des applications ultra-rapides.
Compilation ultra-rapide : Même pour de gros projets, la compilation prend des secondes, pas des minutes. C'est révolutionnaire comparé à C++ ou Java.
Concurrence intégrée : Les goroutines et channels rendent la programmation concurrente aussi simple que du code séquentiel. C'est le super-pouvoir de Go.
Binaire statique : Go compile tout en un seul exécutable sans dépendances. Tu copies le fichier, tu l'exécutes, ça marche. Point final.
Simplicité : Go a volontairement peu de fonctionnalités. Pas d'héritage, pas de génériques (jusqu'à Go 1.18), pas de surcharge de fonctions. Moins de façons de faire = code plus uniforme.
Outillage intégré : go fmt (formatage automatique), go test (tests), go mod (gestion de dépendances) - tout est inclus.
Qui utilise Go ?
Des géants tech ont adopté Go massivement :
-
Uber : Backend de géolocalisation
-
Netflix : Services de streaming
-
Twitch : Chat et infrastructure
-
Dropbox : Migration de Python vers Go pour performance
Si c'est assez bon pour eux, c'est probablement assez bon pour toi !
Installation et premier programme
Commençons par installer Go et créer notre premier programme.
Installation
# macOS (avec Homebrew)
brew install go
# Linux
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
# Windows : télécharger l'installeur sur go.dev
# Vérifier l'installation
go version
Hello World
Créons le programme Go traditionnel :
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
Sauvegarde ce fichier en hello.go et exécute :
go run hello.go
# Output: Hello, Go!
Que se passe-t-il ici ?
-
package main: Chaque fichier Go appartient à un package.mainest spécial - c'est le point d'entrée. -
import "fmt": Importe le package fmt (format) pour l'affichage. -
func main(): La fonction principale, exécutée au démarrage. -
fmt.Println(): Affiche du texte avec un retour à la ligne.
Compiler en binaire
go build hello.go
# Crée un exécutable 'hello'
./hello
# Hello, Go!
Le binaire créé est autonome - tu peux le copier sur n'importe quelle machine (même architecture) et l'exécuter sans rien installer.
Variables et types
Go est un langage typé statiquement - chaque variable a un type défini à la compilation.
Déclaration de variables
Go offre plusieurs façons de déclarer des variables :
// Déclaration explicite
var nom string
nom = "Alice"
// Déclaration avec initialisation
var age int = 25
// Type inféré
var ville = "Paris" // Go devine que c'est un string
// Syntaxe courte (seulement dans les fonctions)
prenom := "Bob" // := déclare ET assigne
score := 100
La syntaxe := est la plus utilisée en Go. Elle déclare la variable et infère son type automatiquement. C'est concis et idiomatique.
Important : := fonctionne seulement dans les fonctions. Au niveau du package, tu dois utiliser var.
Types de base
Go a des types simples et explicites :
// Entiers
var a int = 42 // Taille dépend de l'architecture (32 ou 64 bits)
var b int8 = 127 // -128 à 127
var c int16 = 32767 // -32768 à 32767
var d int32 = 2147483647
var e int64 = 9223372036854775807
// Entiers non signés
var f uint = 42 // Seulement positifs
var g uint8 = 255 // 0 à 255
var h byte = 255 // Alias pour uint8
// Flottants
var prix float32 = 19.99
var pi float64 = 3.14159265359 // Plus précis, préféré
// Booléens
var estVrai bool = true
var estFaux bool = false
// Strings
var message string = "Bonjour"
var multiline = `Ceci est
un texte
multi-lignes` // Backticks pour multi-lignes
// Runes (caractères Unicode)
var lettre rune = 'A' // Alias pour int32
Valeurs par défaut (zero values)
En Go, les variables déclarées sans valeur initiale ont une "zero value" :
var nombre int // 0
var texte string // "" (string vide)
var actif bool // false
var pointeur *int // nil
C'est différent de languages comme JavaScript où une variable non initialisée est undefined. En Go, tout a toujours une valeur valide.
Constantes
Les constantes sont déclarées avec const et ne peuvent pas changer :
const PI = 3.14159
const NOM_APP = "MonApp"
// Constantes groupées
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
Structures de contrôle
if/else : Conditions
Go a une syntaxe if classique mais avec quelques spécificités.
age := 18
if age >= 18 {
fmt.Println("Majeur")
} else {
fmt.Println("Mineur")
}
// if avec initialisation
if score := getScore(); score > 90 {
fmt.Println("Excellent!")
} // score n'existe que dans le bloc if
Pas de parenthèses : Contrairement à C/Java, Go n'utilise pas de parenthèses autour de la condition. Les accolades {} sont obligatoires, même pour une seule ligne.
for : La seule boucle de Go
Go n'a qu'une seule construction de boucle : for. Mais elle peut s'utiliser de plusieurs façons.
// for classique (comme C)
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// for comme while
compteur := 0
for compteur < 10 {
fmt.Println(compteur)
compteur++
}
// Boucle infinie
for {
// Tourne indéfiniment
// Utilise break pour sortir
}
// Itération sur un slice/array
nombres := []int{1, 2, 3, 4, 5}
for index, valeur := range nombres {
fmt.Printf("Index: %d, Valeur: %d\n", index, valeur)
}
// Si on ne veut que les valeurs
for _, valeur := range nombres {
fmt.Println(valeur)
}
Le mot-clé range est super pratique pour itérer sur des collections (slices, maps, strings).
switch : Multi-conditions
Le switch de Go est plus puissant que dans d'autres langages.
jour := "lundi"
switch jour {
case "lundi", "mardi", "mercredi", "jeudi", "vendredi":
fmt.Println("Jour de travail")
case "samedi", "dimanche":
fmt.Println("Weekend")
default:
fmt.Println("Jour invalide")
}
// Switch sans expression (comme if/else)
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("F")
}
Pas de fallthrough : Contrairement à C/Java, les cases en Go ne "tombent pas" automatiquement. Tu n'as pas besoin de break.
Fonctions
Les fonctions sont les blocs de construction fondamentaux en Go.
Syntaxe de base
// Fonction simple
func direBonjour(nom string) {
fmt.Println("Bonjour", nom)
}
// Fonction avec retour
func additionner(a int, b int) int {
return a + b
}
// Paramètres du même type (syntaxe courte)
func multiplier(a, b int) int {
return a * b
}
// Appel
direBonjour("Alice")
resultat := additionner(5, 3)
Le type vient après : Contrairement à C/Java où tu écris int a, en Go c'est a int. Ça peut paraître bizarre au début, mais c'est plus lisible pour les types complexes.
Retours multiples
Go permet de retourner plusieurs valeurs - c'est très courant et idiomatique.
// Fonction retournant deux valeurs
func diviser(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division par zéro")
}
return a / b, nil
}
// Utilisation
resultat, err := diviser(10, 2)
if err != nil {
fmt.Println("Erreur:", err)
} else {
fmt.Println("Résultat:", resultat)
}
C'est le pattern standard en Go pour la gestion d'erreurs. Pas d'exceptions - les erreurs sont des valeurs retournées.
Fonctions variadiques
Les fonctions variadiques acceptent un nombre variable d'arguments.
func somme(nombres ...int) int {
total := 0
for _, n := range nombres {
total += n
}
return total
}
// Utilisation
fmt.Println(somme(1, 2, 3)) // 6
fmt.Println(somme(1, 2, 3, 4, 5)) // 15
Fonctions anonymes et closures
// Fonction anonyme
func main() {
saluer := func(nom string) {
fmt.Println("Salut", nom)
}
saluer("Bob")
// Closure : capture les variables externes
compteur := 0
incrementer := func() int {
compteur++
return compteur
}
fmt.Println(incrementer()) // 1
fmt.Println(incrementer()) // 2
}
Arrays et Slices
Go a deux types de collections similaires mais différents : arrays et slices.
Arrays : Taille fixe
Les arrays ont une taille fixe définie à la compilation.
// Déclaration
var nombres [5]int // Array de 5 ints, initialisé à [0, 0, 0, 0, 0]
// Avec valeurs
fruits := [3]string{"Pomme", "Banane", "Orange"}
// Taille automatique
couleurs := [...]string{"Rouge", "Vert", "Bleu"} // ... calcule la taille
// Accès
fmt.Println(fruits[0]) // Pomme
fruits[1] = "Fraise"
Important : La taille fait partie du type. [3]int et [5]int sont des types différents.
Slices : Arrays dynamiques
Les slices sont comme des arrays mais avec une taille variable. C'est ce que tu utiliseras 99% du temps.
// Créer un slice
nombres := []int{1, 2, 3, 4, 5}
// Slice vide
var vide []int // nil slice
// make pour créer un slice avec capacité
s := make([]int, 5) // longueur 5, valeurs = [0, 0, 0, 0, 0]
s2 := make([]int, 5, 10) // longueur 5, capacité 10
// Ajouter des éléments
nombres = append(nombres, 6)
nombres = append(nombres, 7, 8, 9)
// Longueur et capacité
fmt.Println(len(nombres)) // Nombre d'éléments
fmt.Println(cap(nombres)) // Capacité totale
// Slice d'un slice
sous := nombres[1:4] // Elements index 1, 2, 3 (4 exclu)
fmt.Println(sous) // [2, 3, 4]
Différence clé : Les slices sont des références. Modifier un slice peut affecter d'autres slices qui partagent le même array sous-jacent.
Maps : Dictionnaires
Les maps sont des tables de hachage, comme les dictionnaires Python ou objets JavaScript.
// Créer une map
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
// Map vide
scores := make(map[string]int)
// Ajouter/Modifier
ages["Charlie"] = 35
scores["Niveau1"] = 100
// Accéder
age := ages["Alice"]
fmt.Println(age) // 25
// Vérifier l'existence
age, existe := ages["David"]
if existe {
fmt.Println("Age de David:", age)
} else {
fmt.Println("David n'existe pas")
}
// Supprimer
delete(ages, "Bob")
// Itérer
for nom, age := range ages {
fmt.Printf("%s a %d ans\n", nom, age)
}
Attention : L'ordre d'itération des maps est aléatoire en Go. Ne compte jamais sur un ordre spécifique.
Structs : Types personnalisés
Les structs permettent de créer tes propres types composés.
// Définir une struct
type Personne struct {
Nom string
Age int
Email string
}
// Créer une instance
alice := Personne{
Nom: "Alice",
Age: 25,
Email: "alice@example.com",
}
// Notation courte (ordre des champs)
bob := Personne{"Bob", 30, "bob@example.com"}
// Accéder aux champs
fmt.Println(alice.Nom)
alice.Age = 26
// Pointeur vers struct
p := &alice
p.Email = "newemail@example.com" // Pas besoin de (*p).Email
Méthodes sur structs
En Go, on attache des méthodes aux structs.
type Rectangle struct {
Largeur float64
Hauteur float64
}
// Méthode avec receiver
func (r Rectangle) Aire() float64 {
return r.Largeur * r.Hauteur
}
// Méthode qui modifie (pointer receiver)
func (r *Rectangle) Agrandir(facteur float64) {
r.Largeur *= facteur
r.Hauteur *= facteur
}
// Utilisation
rect := Rectangle{Largeur: 10, Hauteur: 5}
fmt.Println(rect.Aire()) // 50
rect.Agrandir(2)
fmt.Println(rect.Aire()) // 200
Receiver value vs pointer :
-
Value receiver (
r Rectangle) : Reçoit une copie, ne peut pas modifier -
Pointer receiver (
r *Rectangle) : Reçoit une référence, peut modifier
Pointeurs : Références mémoire
Les pointeurs sont des adresses mémoire. Go a des pointeurs mais pas d'arithmétique de pointeurs (plus sûr que C).
// Déclarer un pointeur
var p *int
// & prend l'adresse
x := 42
p = &x
// * déréférence (accède à la valeur)
fmt.Println(*p) // 42
// Modifier via pointeur
*p = 100
fmt.Println(x) // 100 (x a changé)
// new alloue de la mémoire
p2 := new(int)
*p2 = 50
Pourquoi des pointeurs ?
-
Éviter de copier de grandes structures
-
Permettre aux fonctions de modifier des valeurs
-
Représenter l'absence de valeur (nil)
Gestion d'erreurs
Go n'a pas d'exceptions. Les erreurs sont des valeurs retournées.
import (
"errors"
"fmt"
)
// Fonction qui peut échouer
func diviser(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division par zéro")
}
return a / b, nil
}
// Utilisation
resultat, err := diviser(10, 0)
if err != nil {
fmt.Println("Erreur:", err)
return
}
fmt.Println("Résultat:", resultat)
// Pattern idiomatique
if resultat, err := diviser(10, 2); err != nil {
fmt.Println("Erreur:", err)
} else {
fmt.Println("Résultat:", resultat)
}
Ce pattern peut paraître verbeux, mais il rend les erreurs explicites et force leur gestion.
Goroutines : Concurrence facile
Les goroutines sont des threads légers gérés par Go. C'est LE super-pouvoir de Go.
Lancer une goroutine
import (
"fmt"
"time"
)
func direBonjour(nom string) {
for i := 0; i < 3; i++ {
fmt.Println("Bonjour", nom)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// Exécution séquentielle
direBonjour("Alice")
direBonjour("Bob")
// Avec goroutines (concurrent)
go direBonjour("Charlie") // Lance dans une goroutine
go direBonjour("David")
// Attendre un peu sinon main() termine avant les goroutines
time.Sleep(1 * time.Second)
}
Le mot-clé go devant un appel de fonction lance cette fonction dans une nouvelle goroutine.
Attention : Si main() termine, toutes les goroutines sont arrêtées immédiatement.
Channels : Communication entre goroutines
Les channels permettent aux goroutines de communiquer en toute sécurité.
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d traite job %d\n", id, j)
time.Sleep(time.Second)
results <- j * 2 // Envoie le résultat
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan int, 5)
// Lancer 3 workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Envoyer 5 jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// Collecter les résultats
for r := 1; r <= 5; r++ {
result := <-results
fmt.Println("Résultat:", result)
}
}
Les channels sont typés et peuvent être :
-
Unbuffered :
make(chan int)- bloque jusqu'à ce qu'il y ait un receiver -
Buffered :
make(chan int, 10)- peut stocker 10 valeurs avant de bloquer
Interfaces : Polymorphisme
Les interfaces en Go sont implicites - pas besoin de déclarer qu'un type implémente une interface.
// Définir une interface
type Forme interface {
Aire() float64
}
// Rectangle implémente Forme (implicitement)
type Rectangle struct {
Largeur, Hauteur float64
}
func (r Rectangle) Aire() float64 {
return r.Largeur * r.Hauteur
}
// Cercle implémente aussi Forme
type Cercle struct {
Rayon float64
}
func (c Cercle) Aire() float64 {
return 3.14159 * c.Rayon * c.Rayon
}
// Fonction qui accepte n'importe quelle Forme
func afficherAire(f Forme) {
fmt.Printf("Aire: %.2f\n", f.Aire())
}
func main() {
r := Rectangle{Largeur: 10, Hauteur: 5}
c := Cercle{Rayon: 7}
afficherAire(r) // Aire: 50.00
afficherAire(c) // Aire: 153.94
}
Si un type a toutes les méthodes d'une interface, il implémente cette interface automatiquement.
Modules et packages
Depuis Go 1.11, Go utilise les modules pour gérer les dépendances.
Créer un module
# Créer un nouveau module
go mod init monapp
# Ajouter une dépendance
go get github.com/gorilla/mux
# Le fichier go.mod est créé automatiquement
Organiser le code en packages
// fichier: math/operations.go
package math
func Add(a, b int) int {
return a + b
}
// fichier: main.go
package main
import (
"fmt"
"monapp/math" // Import du package local
)
func main() {
result := math.Add(5, 3)
fmt.Println(result)
}
Convention : Les noms exportés (publics) commencent par une majuscule. Les minuscules sont privés au package.
Exemple complet : Serveur HTTP
Créons un serveur web simple - un cas d'usage classique de Go.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Utilisateur struct {
ID int `json:"id"`
Nom string `json:"nom"`
Email string `json:"email"`
}
var utilisateurs = []Utilisateur{
{ID: 1, Nom: "Alice", Email: "alice@example.com"},
{ID: 2, Nom: "Bob", Email: "bob@example.com"},
}
func getUtilisateurs(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(utilisateurs)
}
func accueil(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Bienvenue sur l'API!")
}
func main() {
http.HandleFunc("/", accueil)
http.HandleFunc("/users", getUtilisateurs)
fmt.Println("Serveur démarré sur :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Exécute avec go run main.go et visite http://localhost:8080/users !
Bonnes pratiques
1. Gestion d'erreurs explicite
// ✓ Bon : toujours vérifier les erreurs
file, err := os.Open("fichier.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
2. Utilise defer pour le cleanup
file, err := os.Open("data.txt")
if err != nil {
return err
}
defer file.Close() // Sera exécuté à la fin de la fonction
// Utilise le fichier...
3. Nom des variables courts
// Go préfère les noms courts pour variables locales
for i := 0; i < 10; i++ { } // Pas index
if err != nil { } // Pas error
4. gofmt pour formater
# Formate automatiquement ton code
go fmt ./...
5. Tests intégrés
// fichier: math.go
func Add(a, b int) int {
return a + b
}
// fichier: math_test.go
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
Exécute avec go test.
Conclusion
Go est un langage qui privilégie la simplicité et la clarté. Il n'a pas toutes les fonctionnalités des langages modernes, et c'est volontaire. Ce minimalisme te force à écrire du code simple et maintenable.
Points clés à retenir :
-
Simplicité : Peu de concepts, facile à apprendre
-
Performance : Compile en natif, rapide comme C
-
Concurrence : Goroutines et channels rendent le concurrent facile
-
Outillage : go fmt, go test, go mod - tout intégré
-
Déploiement : Un seul binaire statique
Concepts essentiels :
-
Types statiques avec inférence
-
Gestion d'erreurs explicite (pas d'exceptions)
-
Goroutines pour la concurrence
-
Interfaces implicites
-
Pointeurs sans arithmétique
Go brille particulièrement pour :
-
Services web et APIs
-
Outils CLI
-
Microservices
-
DevOps et infrastructure (Kubernetes, Docker, Terraform)
Maintenant, va créer quelque chose d'incroyable avec Go !
Ressources pour aller plus loin :
-
A Tour of Go - Tutorial officiel interactif
-
Effective Go - Bonnes pratiques
-
Go by Example - Exemples pratiques
Tu as des questions sur Go ? Partage-les dans les commentaires !

Laisser un commentaire