MFormations
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