Cours Go Complet 🐹
Les 12 chapitres essentiels — goroutines, channels, interfaces et simplicité du langage de Google
🏠 Hub Programmation
🐍 Python
⚡ JavaScript
🔷 TypeScript
☁️ AWS Cloud Practitioner
Introduction et premier programme
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.
# 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
| Caractéristique | Go |
|---|---|
| Typage | Statique et fort |
| Paradigme | Impératif, concurrent (pas d'objet classique) |
| Compilation | Compilé en binaire statique unique (très rapide) |
| Mémoire | Garbage collector (mais léger et rapide) |
| Concurrence | Goroutines + Channels — natif et ultra-léger |
| Usage | Cloud (Docker, Kubernetes), APIs, CLI, microservices |
| Convention | camelCase (privé), PascalCase (exporté/public) |
Variables et types de données
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
)
| Type | Zero Value | Exemple |
|---|---|---|
| int / int8 / int16 / int32 / int64 | 0 | var n int = 42 |
| uint / uint8 (byte) / uint16 / uint32 / uint64 | 0 | var b byte = 255 |
| float32 / float64 | 0.0 | var f float64 = 3.14 |
| bool | false | var b bool |
| string | « » (vide) | var s string = « hello » |
| rune | 0 | var 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.
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
Fonctions
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.
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.
Structures de contrôle
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
Slices et Maps
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.
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
Structs et méthodes
// 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
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.
Interfaces
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).
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
Gestion d'erreurs
// 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.
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
Goroutines et Channels
// 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.
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.
Packages et Modules
// 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()
// 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)
Tests et outils
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)
}
}
}
go test ./…
# Avec couverture
go test -cover ./…
# Benchmark
go test -bench=.
Generics et bonnes pratiques
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)
✅ À 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)
| Commande | Action |
|---|---|
| go run | Compiler et exécuter |
| go build | Compiler en binaire |
| go test ./… | Exécuter tous les tests |
| go fmt ./… | Formater le code |
| go vet ./… | Détecter les bugs courants |
| go mod tidy | Nettoyer les dépendances |
| go get pkg | Ajouter une dépendance |
| go doc fmt.Println | Documentation |
ch <- val) et la réception (val := <-ch) sont bloquants sur un channel non bufferisé, ce qui synchronise naturellement les goroutines.🏠 Hub Programmation
🐍 Cours Python
⚡ Cours JavaScript
🔷 Cours TypeScript
☁️ AWS Cloud Practitioner
Cours Go Complet — Goroutines, Channels et simplicité
Référence : go.dev/doc | Go by Example | pkg.go.dev
