Modern Go Engineering
Chapitre 1
01 - Fondamentaux de Go
01 - Fondamentaux de Go
Cours 01 : Fondamentaux de Go
1. Types de base
1.1 Types numériques
var (
a int // platform-dependent (32 ou 64 bits)
b int8 // -128 à 127
c int16 // -32768 à 32767
d int32 // -2147483648 à 2147483647
e int64 // -9223372036854775808 à 9223372036854775807
f uint // non-signé
g uint8 // 0 à 255 (byte)
h uint16 // 0 à 65535
i uint32 // 0 à 4294967295
j uint64 // 0 à 18446744073709551615
k float32 // IEEE-754 32 bits
l float64 // IEEE-754 64 bits
m complex64 // float32 + float32*i
n complex128 // float64 + float64*i
)
1.2 Types texte
var s string // chaîne UTF-8 immuable
var r rune // alias de int32, représente un point de code Unicode
var b byte // alias de uint8
// Strings en détail
s := "Hello, 世界"
fmt.Println(len(s)) // 13 (bytes, pas de caractères)
fmt.Println(utf8.RuneCountInString(s)) // 9 (runes)
fmt.Println(s[0]) // 72 ('H' en ASCII)
// Itération correcte
for i, r := range s {
fmt.Printf("position %d: %c (rune %U)\n", i, r, r)
}
1.3 Types booléen
var b bool // true ou false, pas de conversion avec les entiers
var c bool = true
d := 5 > 3 // true
1.4 Zero values
var (
i int // 0
f float64 // 0
s string // ""
b bool // false
p *int // nil
sl []int // nil (mais len(sl) == 0)
m map[int]string // nil
ch chan int // nil
st struct{} // zero value de tous ses champs
)
2. Variables et déclarations
2.1 var et :=
// Déclaration explicite
var name string = "Alice"
// Inférence de type
var name = "Alice"
// Syntaxe courte (dans les fonctions)
name := "Alice"
// Déclarations multiples
var x, y int = 1, 2
var (
name string = "Alice"
age int = 30
active bool = true
)
2.2 Constantes
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
// iota pour les énumérations
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
2.3 Portée des variables
package main
var globalVar = "accessible partout dans le package"
func main() {
localVar := "accessible seulement dans main"
if x := compute(); x > 0 {
// x accessible ici
innerVar := "dans le if"
fmt.Println(innerVar)
}
// x n'est plus accessible
// innerVar n'est plus accessible
}
func compute() int { return 42 }
3. Pointeurs
3.1 Concepts de base
x := 42
p := &x // p est un *int, pointe vers x
fmt.Println(*p) // 42 (déréférencement)
*p = 21 // modifie x via le pointeur
fmt.Println(x) // 21
3.2 new vs &
// Les deux allouent et retournent un pointeur
p1 := new(int) // *int pointant vers 0
*p1 = 42
p2 := &int{} // *int pointant vers 0 (équivalent)
p3 := &int{42} // *int pointant vers 42
3.3 Pointeurs et fonctions
// Passage par valeur
func incrementCopy(x int) {
x++ // ne modifie pas l'original
}
// Passage par pointeur
func incrementPtr(x *int) {
*x++ // modifie l'original
}
// Retour multiple
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
3.4 Quand utiliser des pointeurs
// 1. Pour modifier l'original
func updateName(u *User, name string) {
u.Name = name
}
// 2. Pour éviter la copie de grandes structures
type LargeStruct struct {
data [1024]byte
}
func process(l *LargeStruct) { /* pas de copie */ }
// 3. Pour les champs optionnels (nil = absent)
type Config struct {
Timeout *int // nil si pas spécifié
Debug *bool // nil si pas spécifié
}
// 4. Les types référence (slice, map, channel) sont déjà des références
func modifySlice(s []int) {
s[0] = 999 // l'appelant voit la modification
}
4. Fonctions
4.1 Déclarations
// Fonction simple
func add(a, b int) int {
return a + b
}
// Multi-retour
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Retours nommés
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // naked return (déconseillé pour les fonctions longues)
}
4.2 Fonctions variadiques
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
nums := []int{1, 2, 3}
fmt.Println(sum(nums...)) // spread operator
}
4.3 Defer
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // exécuté quand la fonction retourne
// Traitement...
data, err := io.ReadAll(f)
return err
}
// Ordre LIFO
func example() {
defer fmt.Println("1") // dernier exécuté
defer fmt.Println("2")
defer fmt.Println("3") // premier exécuté
}
// Affiche: 3, 2, 1
4.4 Closures
// Closure simple
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
c := counter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3
// Factory
func makeGreeter(greeting string) func(string) string {
return func(name string) string {
return greeting + ", " + name
}
}
french := makeGreeter("Bonjour")
english := makeGreeter("Hello")
fmt.Println(french("Alice")) // Bonjour, Alice
fmt.Println(english("Bob")) // Hello, Bob
4.5 Fonctions comme valeurs
type Operation func(int, int) int
func apply(a, b int, op Operation) int {
return op(a, b)
}
func main() {
add := func(a, b int) int { return a + b }
sub := func(a, b int) int { return a - b }
fmt.Println(apply(5, 3, add)) // 8
fmt.Println(apply(5, 3, sub)) // 2
fmt.Println(apply(5, 3, func(a, b int) int { return a * b })) // 15
}
5. Structs et Méthodes
5.1 Définition de structs
type Person struct {
Name string
Age int
Email string
Active bool
}
// Initialisation
func main() {
// Ordre des champs
p1 := Person{"Alice", 30, "alice@email.com", true}
// Champs nommés (recommandé)
p2 := Person{
Name: "Bob",
Age: 25,
Email: "bob@email.com",
}
// Zero value
var p3 Person
// Constructeur idiomatique
p4 := NewPerson("Charlie", 35)
}
func NewPerson(name string, age int) Person {
return Person{
Name: name,
Age: age,
Email: fmt.Sprintf("%s@email.com", strings.ToLower(name)),
Active: true,
}
}
5.2 Méthodes
type Rectangle struct {
Width float64
Height float64
}
// Value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Pointer receiver (pour modifier ou éviter la copie)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func (r *Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Méthode avec validation
func (r *Rectangle) SetWidth(w float64) error {
if w <= 0 {
return fmt.Errorf("width must be positive, got %f", w)
}
r.Width = w
return nil
}
5.3 Composition (au lieu d'héritage)
type Address struct {
Street string
City string
Country string
}
type Employee struct {
Person // Embedding : Employee "hérite" de Person
Position string
Salary float64
Address // Adresse de travail
}
func main() {
e := Employee{
Person: Person{
Name: "Alice",
Age: 30,
},
Position: "Engineer",
Salary: 75000,
Address: Address{
Street: "123 Main St",
City: "Paris",
},
}
// Accès direct aux champs de Person
fmt.Println(e.Name) // Promu automatiquement
fmt.Println(e.Person.Age) // Accès explicite
}
6. Interfaces
6.1 Satisfaction implicite
type Writer interface {
Write([]byte) (int, error)
}
type ConsoleWriter struct{}
func (cw ConsoleWriter) Write(data []byte) (int, error) {
n, err := fmt.Println(string(data))
return n, err
}
// Pas de mot-clé "implements" !
var w Writer = ConsoleWriter{}
w.Write([]byte("Hello, Interface!"))
6.2 Interface vide
// any = interface{} (depuis Go 1.18)
var v any
v = 42
v = "hello"
v = Person{Name: "Alice"}
// Type assertion
name := v.(Person).Name
// Type switch
switch val := v.(type) {
case int:
fmt.Printf("int: %d\n", val)
case string:
fmt.Printf("string: %s\n", val)
case Person:
fmt.Printf("Person: %s\n", val.Name)
default:
fmt.Printf("unknown type: %T\n", val)
}
6.3 Interfaces composées
// Composition d'interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type ReadCloser interface {
Reader
Closer
}
// io.ReadWriter, io.ReadCloser, etc. suivent ce pattern
6.4 Interface nil vs pointeur nil
type Fooer interface {
Foo()
}
type Bar struct{}
func (b *Bar) Foo() {
fmt.Println("Bar.Foo")
}
func main() {
var b *Bar = nil
var f Fooer = b
fmt.Println(b == nil) // true
fmt.Println(f == nil) // false !!
// f est non-nil car il contient un type (*Bar) même si la valeur est nil
}
7. Gestion des erreurs
7.1 L'interface error
type error interface {
Error() string
}
// Création d'erreurs
err1 := errors.New("something went wrong")
err2 := fmt.Errorf("user %d not found", 42)
7.2 Sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrPermission = errors.New("permission denied")
ErrTimeout = errors.New("request timeout")
)
func FindUser(id int) (User, error) {
if id <= 0 {
return User{}, ErrNotFound
}
// ...
}
func main() {
_, err := FindUser(-1)
if errors.Is(err, ErrNotFound) {
fmt.Println("User not found, creating default...")
}
}
7.3 Error wrapping
type Config struct {
Path string
}
func ReadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %s: %w", path, err)
}
// ...
}
func main() {
cfg, err := ReadConfig("/etc/app/config.json")
if err != nil {
// Affiche toute la chaîne d'erreurs
fmt.Println(err)
// Vérifie si l'erreur originale est un os.ErrNotExist
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Fichier de config manquant, utilisation des valeurs par défaut")
cfg = &Config{Path: "/etc/app/config.json"}
}
// Type assertion pour erreur spécifique
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Erreur sur le chemin %s: %v\n", pathErr.Path, pathErr.Err)
}
}
}
7.4 Erreurs personnalisées
type ValidationError struct {
Field string
Value any
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s (got %v)", e.Field, e.Message, e.Value)
}
func validateAge(age int) error {
if age < 0 {
return &ValidationError{
Field: "age",
Value: age,
Message: "must be non-negative",
}
}
if age > 150 {
return &ValidationError{
Field: "age",
Value: age,
Message: "must be less than 150",
}
}
return nil
}
7.5 Panic et Recover
// panic = erreur irrécupérable (utilisation rare et contrôlée)
func must(err error) {
if err != nil {
panic(err)
}
}
// recover = récupération (utile surtout dans les middlewares HTTP)
func safeCall(f func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recovered: %v", r)
}
}()
f()
return
}
8. Packages et Modules
8.1 Structure de package
// math/operations.go
package math
func Add(a, b int) int { return a + b } // exportée
func subtract(a, b int) int { return a - b } // non exportée
8.2 go.mod
module github.com/user/project
go 1.23
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
)
8.3 Import
import (
"fmt"
"os"
"github.com/user/project/math"
myfmt "github.com/user/project/format" // alias
_ "github.com/user/project/init" // import pour init() seulement
. "github.com/user/project/assert" // import point (déconseillé)
)
Résumé
- Types numériques explicités (int8, int16, int32, int64)
- Zero values pour toutes les variables
- Pointeurs pour modification/optimisation
- Functions multi-retour pour les erreurs
- Defer pour le nettoyage
- Composition plutôt qu'héritage
- Interfaces implicites
- Erreurs comme valeurs
- Packages et modules organisés via go.mod