Cours Go Complet 🐹

Les 12 chapitres essentiels — goroutines, channels, interfaces et simplicité du langage de Google

12
Chapitres
100+
Exemples de code
Go 1.22+
Version
A1→C2
Niveaux

CHAPITRE 01

Introduction et premier programme

🐹 Hello World
package main

import « fmt »

func main()

Go est conçu pour la simplicité. Pas de classes, pas d'héritage, pas d'exceptions, pas de generics complexes. Un seul style de boucle (for). Le formateur gofmt impose un style unique. Le compilateur refuse les imports et variables non utilisés.

⚙️ Installer et exécuter
# Installer Go : https://go.dev/dl/

# Créer un module
mkdir monprojet && cd monprojet
go mod init monprojet

# Structure :
# monprojet/
# ├── go.mod (module + dépendances)
# └── main.go (code source)

# Exécuter directement
go run main.go

# Compiler en binaire
go build -o app

# Formater le code (OBLIGATOIRE en Go)
gofmt -w .

# Ajouter une dépendance
go get github.com/gin-gonic/gin

🏗️ Go en résumé
CaractéristiqueGo
TypageStatique et fort
ParadigmeImpératif, concurrent (pas d'objet classique)
CompilationCompilé en binaire statique unique (très rapide)
MémoireGarbage collector (mais léger et rapide)
ConcurrenceGoroutines + Channels — natif et ultra-léger
UsageCloud (Docker, Kubernetes), APIs, CLI, microservices
ConventioncamelCase (privé), PascalCase (exporté/public)

Go vs les autres : Compilé comme le C (rapide), simple comme Python (lisible), concurrent par design. Docker, Kubernetes, Terraform et Hugo sont écrits en Go.

🧠 Quiz
Comment Go gère la visibilité (public/private) ?
Par la casse de la première lettre. Majuscule = exporté (public). minuscule = non exporté (privé au package). Pas de mots-clés public/private.

CHAPITRE 02

Variables et types de données

📦 Déclaration de variables
// var — déclaration explicite
var nom string = « Alice »
var age int = 25
var actif bool // false (zero value)

// := — raccourci (inférence de type, DANS une fonction uniquement)
nom := « Alice » // string
age := 25 // int
pi := 3.14 // float64
actif := true // bool

// Déclaration multiple
var (
x int = 1
y float64 = 2.5
z string = « hello »
)

// Constantes
const Pi = 3.14159
const (
StatusOK = 200
StatusError = 500
)

🔢 Types de données
TypeZero ValueExemple
int / int8 / int16 / int32 / int640var n int = 42
uint / uint8 (byte) / uint16 / uint32 / uint640var b byte = 255
float32 / float640.0var f float64 = 3.14
boolfalsevar b bool
string«  » (vide)var s string = « hello »
rune0var r rune = 'A' (int32, Unicode)

Zero values : En Go, toute variable déclarée est automatiquement initialisée à sa zero value (0, false, «  », nil). Pas de variable « undefined » comme en JavaScript.

📝 Strings
import (« fmt »; « strings »; « strconv »)

s := « Hello World »

len(s) // 11 (octets, pas caractères !)
s[0] // 72 (byte 'H')
s[0:5] // « Hello » (slice)

strings.Contains(s, « World ») // true
strings.HasPrefix(s, « He ») // true
strings.HasSuffix(s, « ld ») // true
strings.ToUpper(s) // « HELLO WORLD »
strings.ToLower(s) // « hello world »
strings.TrimSpace( » hi « ) // « hi »
strings.Split(s,  » « ) // [« Hello », « World »]
strings.Join(parts, « -« ) // « Hello-World »
strings.Replace(s, « World », « Go », 1)
strings.Count(s, « l ») // 3

// Formatage
msg := fmt.Sprintf(« Nom: %s, Age: %d », « Alice », 25)

// Conversions
n, err := strconv.Atoi(« 42 ») // string → int
s := strconv.Itoa(42) // int → string

CHAPITRE 03

Fonctions

🔧 Fonctions en Go
// Fonction basique
func add(a int, b int) int

// Paramètres de même type — raccourci
func add(a, b int) int

// Retours multiples — L'IDIOME Go
func divide(a, b float64) (float64, error)

result, err := divide(10, 3)
if err != nil

// _ pour ignorer un retour
result, _ := divide(10, 3)

// Retours nommés
func swap(a, b string) (first, second string)

// Variadic (nombre variable d'arguments)
func sum(nums …int) int
sum(1, 2, 3) // 6

Les retours multiples sont le cœur de Go. Le pattern (value, error) remplace les exceptions. Chaque fonction qui peut échouer retourne une erreur que l'appelant doit explicitement gérer.

⚡ Fonctions anonymes et defer
// Fonction anonyme / closure
double := func(n int) int
double(5) // 10

// defer — exécuté à la FIN de la fonction (LIFO)
func readFile(path string) error

defer garantit qu'une ressource est nettoyée quoi qu'il arrive (même en cas de panic). C'est l'équivalent Go du try/finally. Utilisez-le immédiatement après l'ouverture d'une ressource.

CHAPITRE 04

Structures de contrôle

🔀 if, switch, for
// if — pas de parenthèses, accolades obligatoires
if age >= 18 else

// if avec initialisation (scope limité au if)
if err := doSomething(); err != nil

// switch — PAS de break (implicite), pas de fallthrough par défaut
switch day

// switch sans condition (remplace if/else if)
switch

// for — LA SEULE boucle en Go
for i := 0; i < 5; i++ // for classique
for i < 10 // while
for // boucle infinie
for i, v := range slice // itération (index, valeur)
for _, v := range slice // ignorer l'index
for k, v := range myMap // itérer sur une map

CHAPITRE 05

Slices et Maps

📋 Arrays et Slices
// Array — taille FIXE (rarement utilisé directement)
var arr [5]int = [5]int

// Slice — vue dynamique sur un array (LE type utilisé)
s := []int
s = append(s, 4, 5) // [1, 2, 3, 4, 5]
len(s) // 5 (longueur)
cap(s) // Capacité sous-jacente

// make — créer un slice avec taille et capacité
s := make([]int, 5) // [0, 0, 0, 0, 0] (len=5, cap=5)
s := make([]int, 0, 10) // [] (len=0, cap=10) — pré-alloué

// Slicing
s[1:3] // éléments 1 et 2
s[:3] // du début à 2
s[2:] // de 2 à la fin

// Copier
dst := make([]int, len(s))
copy(dst, s)

// Supprimer un élément à l'index i
s = append(s[:i], s[i+1:]…)

Piège : Un slice est une référence vers un array sous-jacent. Modifier un slice modifie le même array. Si vous passez un slice à une fonction, elle peut modifier les données originales. Utilisez copy() pour une copie indépendante.

🗺️ Maps
// Créer une map
ages := map[string]int

// Ou avec make
ages := make(map[string]int)

// CRUD
ages[« Charlie »] = 28 // Insérer / modifier
age := ages[« Alice »] // Lire (0 si absent)
delete(ages, « Bob ») // Supprimer
len(ages) // Nombre d'éléments

// Vérifier l'existence (comma ok idiom)
age, ok := ages[« Eve »] if !ok

// Itérer
for nom, age := range ages

🧠 Quiz
Différence entre un array et un slice en Go ?
Array = taille fixe, type inclut la taille ([5]int ≠ [3]int). Slice = taille dynamique, référence un array sous-jacent, redimensionnable avec append. En pratique, on utilise quasiment toujours les slices.

CHAPITRE 06

Structs et méthodes

📦 Définir une struct
type Person struct

// Créer
alice := Person
bob := Person // Par position (déconseillé)

// Accès
alice.Name // « Alice »
alice.Age = 26

// Pointeur vers struct
p := &Person
p.Name // « Charlie » (Go déréférence automatiquement)

// Constructeur (convention — pas de mot-clé)
func NewPerson(name string, age int) *Person

🔧 Méthodes (receivers)
// Value receiver — ne modifie PAS l'original
func (p Person) Greet() string

// Pointer receiver — MODIFIE l'original
func (p *Person) Birthday()

alice := Person
fmt.Println(alice.Greet()) // « Bonjour, je suis Alice »
alice.Birthday() // alice.Age = 26

// Composition (pas d'héritage en Go — embedding)
type Employee struct

emp := Employee{
Person: Person,
Company: « Google »,
}
emp.Name // « Alice » (promu depuis Person)
emp.Greet() // « Bonjour, je suis Alice »

Go n'a pas d'héritage classique. À la place, il utilise la composition (embedding). Un type peut être embarqué dans un autre, et ses champs/méthodes sont « promus » au niveau parent. C'est plus simple et plus flexible.

CHAPITRE 07

Interfaces

📋 Interfaces implicites
// Interface = ensemble de méthodes
type Shape interface

// Circle implémente Shape IMPLICITEMENT
// (pas de « implements » — si les méthodes existent, c'est bon)
type Circle struct

func (c Circle) Area() float64
func (c Circle) Perimeter() float64

// Utiliser l'interface
func printShape(s Shape)

printShape(Circle) // ✅ Circle implémente Shape

Interfaces implicites = le génie de Go. Aucun implements nécessaire. Si un type a les bonnes méthodes, il satisfait l'interface automatiquement. Cela encourage les petites interfaces (io.Reader = une seule méthode).

⚡ L'interface vide et type assertions
// any (= interface) — accepte n'importe quel type
func printAnything(v any)

// Type assertion — récupérer le type concret
var i any = « hello »
s, ok := i.(string) // ok = true, s = « hello »
n, ok := i.(int) // ok = false, n = 0

// Type switch
switch v := i.(type)

// Interfaces standard importantes
// io.Reader — Read(p []byte) (n int, err error)
// io.Writer — Write(p []byte) (n int, err error)
// fmt.Stringer — String() string (comme toString())
// error — Error() string

🧠 Quiz
Pourquoi les interfaces Go sont dites « implicites » ?
Parce qu'un type n'a pas besoin de déclarer qu'il implémente une interface. Il suffit que le type possède toutes les méthodes définies par l'interface. Le compilateur vérifie automatiquement la compatibilité.

CHAPITRE 08

Gestion d'erreurs

🛡️ Le pattern error en Go
import (« errors »; « fmt »; « os »)

// error est une INTERFACE : Error() string

// Créer une erreur simple
err := errors.New(« quelque chose a échoué »)
err := fmt.Errorf(« utilisateur %s non trouvé », name)

// Pattern standard : if err != nil
f, err := os.Open(« data.txt »)
if err != nil
defer f.Close()

// Erreur custom
type NotFoundError struct

func (e *NotFoundError) Error() string

// Vérifier le type d'erreur
var nfe *NotFoundError
if errors.As(err, &nfe)

// Vérifier une erreur sentinel
if errors.Is(err, os.ErrNotExist)

Go n'a pas d'exceptions. La gestion d'erreur est explicite et verboseuse (if err != nil partout). C'est un choix de design : forcer le développeur à traiter chaque erreur. Le %w dans Errorf permet de wrapper les erreurs pour tracer l'origine.

⚠️ panic et recover
// panic — erreur irrécupérable (comme throw en Java)
func mustOpen(path string) *os.File

// recover — attraper un panic (dans un defer)
func safeCall()

// Règle : panic pour les bugs du programme, error pour les erreurs attendues

CHAPITRE 09

Goroutines et Channels

🔀 Goroutines — concurrence ultra-légère
import (« fmt »; « sync »; « time »)

// go = lance une goroutine (thread ultra-léger, ~2 Ko de stack)
func worker(id int)

func main()

Les goroutines sont le super-pouvoir de Go. Contrairement aux threads OS (~1 Mo de stack), une goroutine ne pèse que ~2 Ko. Vous pouvez en lancer des millions. Le scheduler Go les multiplex sur les threads OS.

📡 Channels — communication entre goroutines
// Channel = pipe typé entre goroutines
ch := make(chan int) // Non bufferisé (bloquant)
ch := make(chan int, 10) // Bufferisé (jusqu'à 10 valeurs)

// Envoyer et recevoir
go func() ()
val := <-ch // Recevoir (bloque jusqu'à réception)

// Itérer sur un channel
go func() ()
for val := range ch

// select — attendre sur plusieurs channels
select

« Don't communicate by sharing memory, share memory by communicating. » — Proverbe Go. Plutôt que de protéger des données partagées avec des mutex, envoyez les données entre goroutines via des channels.

CHAPITRE 10

Packages et Modules

📦 Organisation d'un projet
// Structure typique
// monprojet/
// ├── go.mod
// ├── go.sum
// ├── main.go
// ├── internal/ (privé au module)
// │ └── db/
// │ └── db.go
// └── pkg/ (exportable)
// └── utils/
// └── utils.go

// utils.go
package utils

// Fonction exportée (majuscule)
func FormatName(name string) string

// Fonction privée (minuscule)
func helper()

// main.go
package main

import « monprojet/pkg/utils »

func main()

📄 JSON et HTTP
import (« encoding/json »; « net/http »)

// Struct avec tags JSON
type User struct

// Marshal (Go → JSON)
u := User
data, _ := json.Marshal(u)
//

// Unmarshal (JSON → Go)
var u2 User
json.Unmarshal(data, &u2)

// Serveur HTTP minimal
http.HandleFunc(« /api/users », func(w http.ResponseWriter, r *http.Request) )
http.ListenAndServe(« :8080 », nil)

CHAPITRE 11

Tests et outils

🧪 Tests intégrés
// math.go
package math

func Add(a, b int) int

// math_test.go — fichier _test.go automatiquement reconnu
package math

import « testing »

func TestAdd(t *testing.T)

// Table-driven tests (pattern Go standard)
func TestAddTable(t *testing.T) {
tests := []struct {
a, b, want int
}{
,
{0, 0, 0},
{1, 1, 0},
}
for _, tt := range tests {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf(« Add(%d, %d) = %d, want %d », tt.a, tt.b, got, tt.want)
}
}
}

# Exécuter les tests
go test ./…

# Avec couverture
go test -cover ./…

# Benchmark
go test -bench=.

CHAPITRE 12

Generics et bonnes pratiques

🔄 Generics (Go 1.18+)
// Fonction générique avec contrainte
func Min[T cmp.Ordered](a, b T) T

Min(3, 7) // 3
Min(« a », « b ») // « a »
Min(3.14, 2.71) // 2.71

// Contrainte personnalisée
type Number interface

func Sum[T Number](nums []T) T

// Struct générique
type Stack[T any] struct

func (s *Stack[T]) Push(item T)

func (s *Stack[T]) Pop() (T, bool)

✅ Bonnes pratiques

✅ À FAIRE
gofmt / goimports (formatage)
go vet (détection de bugs)
golint / staticcheck
• Toujours vérifier err != nil
defer pour fermer les ressources
• Petites interfaces (1-2 méthodes)
• Table-driven tests
context.Context pour les timeouts
• Composition plutôt qu'héritage

❌ À ÉVITER
• Ignorer les erreurs (_, _ = …)
panic pour les erreurs attendues
init() abusif (effets de bord cachés)
any partout (perd le type safety)
• Goroutines sans WaitGroup
• Channels non fermés (goroutine leak)
• Imports non utilisés (ne compile pas)
• Variables non utilisées (ne compile pas)
• Noms trop longs (Go préfère court)

🔧 Commandes Go essentielles
CommandeAction
go runCompiler et exécuter
go buildCompiler en binaire
go test ./…Exécuter tous les tests
go fmt ./…Formater le code
go vet ./…Détecter les bugs courants
go mod tidyNettoyer les dépendances
go get pkgAjouter une dépendance
go doc fmt.PrintlnDocumentation
🧠 Quiz
Quelle est la philosophie de design de Go ?
Simplicité et lisibilité. Un seul type de boucle (for), pas de classes, pas d'héritage, formatage imposé (gofmt), erreurs explicites. Le but : que tout développeur puisse lire le code de n'importe quel autre développeur Go immédiatement.
Qu'est-ce qu'un channel en Go ?
Un tube typé qui permet à deux goroutines de communiquer en s'envoyant des valeurs. L'envoi (ch <- val) et la réception (val := <-ch) sont bloquants sur un channel non bufferisé, ce qui synchronise naturellement les goroutines.

Cours Go Complet — Goroutines, Channels et simplicité

Référence : go.dev/doc | Go by Example | pkg.go.dev