MFormations
Modern Go Engineering

Chapitre 3

03 - Go Standard Library

03 - Go Standard Library

Cours 03 : Go Standard Library

1. Package fmt

1.1 Formatage

// Verbes principaux
fmt.Printf("%v\n", 42)        // valeur par défaut
fmt.Printf("%+v\n", user)     // avec noms de champs
fmt.Printf("%#v\n", user)     // représentation Go
fmt.Printf("%T\n", user)      // type

// Types spécifiques
fmt.Printf("%d\n", 42)        // entier décimal
fmt.Printf("%x\n", 255)       // hexadécimal
fmt.Printf("%f\n", 3.14)      // flottant
fmt.Printf("%.2f\n", 3.14159) // précision
fmt.Printf("%s\n", "hello")   // string
fmt.Printf("%q\n", "hello")   // string quotée
fmt.Printf("%08b\n", 5)       // binaire avec padding

1.2 Fprintf, Sprintf, Fscanf

// Écriture dans un writer
fmt.Fprintf(os.Stdout, "Hello %s\n", "world")

// Formatage en string
s := fmt.Sprintf("Value: %d", 42)

// Lecture formatée
var name string
var age int
fmt.Sscanf("Alice 30", "%s %d", &name, &age)

2. Package io

2.1 Reader et Writer

// Interface Reader
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Interface Writer
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Implémentation avec strings.Reader
r := strings.NewReader("Hello, World!")
buf := make([]byte, 5)
n, err := r.Read(buf)
fmt.Printf("Lu %d bytes: %s\n", n, buf[:n])

2.2 io.Copy

// Copie de reader vers writer
resp, _ := http.Get("https://example.com")
defer resp.Body.Close()

// Copie directe vers stdout
io.Copy(os.Stdout, resp.Body)

// Copie avec buffer
io.CopyBuffer(os.Stdout, resp.Body, make([]byte, 4096))

// Copie avec limite
io.CopyN(os.Stdout, resp.Body, 1024)

2.3 bufio

// Lecture bufferisée
f, _ := os.Open("file.txt")
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
    fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
    log.Fatal(err)
}

// Écriture bufferisée
w := bufio.NewWriter(os.Stdout)
w.WriteString("Hello, ")
w.WriteString("World!\n")
w.Flush() // Important : vider le buffer

3. Package strings et strconv

3.1 strings

s := "Hello, World!"

strings.Contains(s, "World")      // true
strings.HasPrefix(s, "Hello")     // true
strings.HasSuffix(s, "!")         // true
strings.Index(s, "World")         // 7
strings.Split(s, ", ")            // ["Hello", "World!"]
strings.Join([]string{"a", "b"}, ",") // "a,b"
strings.ReplaceAll(s, "World", "Go") // "Hello, Go!"
strings.ToUpper(s)                // "HELLO, WORLD!"
strings.TrimSpace("  hello  ")    // "hello"
strings.Fields("a b c")          // ["a", "b", "c"]

// Builder efficace
var b strings.Builder
b.WriteString("Hello")
b.WriteByte(' ')
b.WriteString("World")
fmt.Println(b.String())

3.2 strconv

// String ↔ int
i, _ := strconv.Atoi("42")          // 42
s := strconv.Itoa(42)               // "42"

// Parse
f, _ := strconv.ParseFloat("3.14", 64) // 3.14
b, _ := strconv.ParseBool("true")      // true
i, _ := strconv.ParseInt("FF", 16, 64) // 255

// Format
s := strconv.FormatInt(255, 16)     // "ff"
s := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"

4. Package time

4.1 Time et Duration

now := time.Now()
fmt.Println(now.Format(time.RFC3339)) // 2024-01-15T10:30:00+01:00

// Duration
d := 5 * time.Second + 500 * time.Millisecond
fmt.Println(d)           // 5.5s
fmt.Println(d.Seconds()) // 5.5
fmt.Println(d.Milliseconds()) // 5500

// Parsing
t, _ := time.Parse("2006-01-02", "2024-01-15")

// Calculs
tomorrow := now.Add(24 * time.Hour)
diff := tomorrow.Sub(now)
fmt.Printf("%.0f heures\n", diff.Hours())

4.2 Ticker et Timer

// Timer (une fois)
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("Timer expired")

// Ticker (périodique)
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)

go func() {
    for {
        select {
        case t := <-ticker.C:
            fmt.Println("Tick at", t)
        case <-done:
            return
        }
    }
}()

time.Sleep(2 * time.Second)
ticker.Stop()
done <- true

// After (simple)
select {
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
case result := <-ch:
    fmt.Println(result)
}

5. Package net/http

5.1 Client HTTP

client := &http.Client{
    Timeout: 10 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 90 * time.Second,
    },
}

// GET
resp, err := client.Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %d, Body: %s\n", resp.StatusCode, body)

// POST avec JSON
data := map[string]any{"name": "Alice"}
jsonData, _ := json.Marshal(data)
resp, err = client.Post(
    "https://api.example.com/users",
    "application/json",
    bytes.NewReader(jsonData),
)

5.2 RoundTripper et Middleware

type LoggingTransport struct {
    Transport http.RoundTripper
}

func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    start := time.Now()
    log.Printf("Request: %s %s", req.Method, req.URL)
    
    resp, err := t.Transport.RoundTrip(req)
    
    log.Printf("Response: %d in %v", resp.StatusCode, time.Since(start))
    return resp, err
}

client := &http.Client{
    Transport: &LoggingTransport{
        Transport: http.DefaultTransport,
    },
}

6. Package encoding/json

6.1 Marshal/Unmarshal

type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    Password  string    `json:"-"`
}

// Marshal
user := User{ID: 1, Name: "Alice", CreatedAt: time.Now()}
data, _ := json.Marshal(user)
fmt.Println(string(data))

data, _ = json.MarshalIndent(user, "", "  ")
fmt.Println(string(data))

// Unmarshal
jsonData := `{"id":1,"name":"Alice","email":"alice@example.com"}`
var u User
json.Unmarshal([]byte(jsonData), &u)

6.2 Encoder/Decoder

// Encoder vers writer
f, _ := os.Create("users.json")
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", "  ")
encoder.Encode(user)

// Decoder depuis reader
f, _ = os.Open("users.json")
defer f.Close()
decoder := json.NewDecoder(f)
var users []User
decoder.Decode(&users)

6.3 Interfaces et JSON

type Shape interface {
    Area() float64
}

type Circle struct {
    Radius float64 `json:"radius"`
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// Désérialisation polymorphique
type ShapeWrapper struct {
    Type   string          `json:"type"`
    Config json.RawMessage `json:"config"`
}

func decodeShape(raw json.RawMessage, shapeType string) (Shape, error) {
    switch shapeType {
    case "circle":
        var c Circle
        json.Unmarshal(raw, &c)
        return c, nil
    default:
        return nil, fmt.Errorf("unknown shape: %s", shapeType)
    }
}

7. Package context

7.1 Création et propagation

type contextKey string

const (
    TraceIDKey contextKey = "trace_id"
    UserIDKey  contextKey = "user_id"
)

func WithTraceID(ctx context.Context, traceID string) context.Context {
    return context.WithValue(ctx, TraceIDKey, traceID)
}

func GetTraceID(ctx context.Context) (string, bool) {
    traceID, ok := ctx.Value(TraceIDKey).(string)
    return traceID, ok
}

func handler(ctx context.Context) {
    ctx = WithTraceID(ctx, uuid.New().String())
    
    // Propagation
    result, err := databaseQuery(ctx)
    if err != nil {
        log.Printf("trace=%s: %v", GetTraceID(ctx), err)
    }
}

7.2 Deadlines et annulation

func fetchWithRetry(ctx context.Context, url string) (*http.Response, error) {
    var lastErr error
    for i := 0; i < 3; i++ {
        select {
        case <-ctx.Done():
            return nil, ctx.Err()
        default:
        }
        
        req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
        resp, err := http.DefaultClient.Do(req)
        if err == nil {
            return resp, nil
        }
        lastErr = err
        
        time.Sleep(time.Duration(math.Pow(2, float64(i))) * 100 * time.Millisecond)
    }
    return nil, fmt.Errorf("all retries failed: %w", lastErr)
}

8. Package reflect

8.1 Type et Value

func inspect(v any) {
    t := reflect.TypeOf(v)
    val := reflect.ValueOf(v)
    
    fmt.Printf("Type: %s, Kind: %s\n", t, t.Kind())
    
    // Itération sur les champs d'une struct
    if t.Kind() == reflect.Struct {
        for i := 0; i < t.NumField(); i++ {
            field := t.Field(i)
            fval := val.Field(i)
            fmt.Printf("  Field %d: %s (%s) = %v [tag: %s]\n",
                i, field.Name, field.Type, fval.Interface(), field.Tag)
        }
    }
}

type Config struct {
    Host string `json:"host" default:"localhost"`
    Port int    `json:"port" default:"8080"`
}

func main() {
    inspect(Config{Host: "example.com"})
}

8.2 Création dynamique

func populateDefaults(v any) {
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    
    t := val.Type()
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fval := val.Field(i)
        
        if defaultVal, ok := field.Tag.Lookup("default"); ok {
            if fval.IsZero() {
                switch fval.Kind() {
                case reflect.String:
                    fval.SetString(defaultVal)
                case reflect.Int:
                    n, _ := strconv.Atoi(defaultVal)
                    fval.SetInt(int64(n))
                }
            }
        }
    }
}

8.3 Limitations

// reflect ne peut pas :
// - Créer des types génériques
// - Accéder aux variables non exportées
// - Être utilisé avec des génériques simplement
// - Garantir la performance (10-100x plus lent)

// Préférer :
// - gob/encoding pour la sérialisation
// - code generation (sqlc, ogen)
// - interfaces pour le polymorphisme

9. Package sort, container, math, crypto

9.1 sort

ints := []int{5, 2, 8, 1, 9}
sort.Ints(ints)

strings := []string{"banana", "apple", "cherry"}
sort.Strings(strings)

// Custom sort
type ByAge []User
func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

sort.Sort(ByAge(users))
sort.Slice(users, func(i, j int) bool {
    return users[i].Age < users[j].Age
})

9.2 container

// heap
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
fmt.Println(heap.Pop(h)) // 1

// list (double chaînée)
l := list.New()
l.PushBack("a")
l.PushFront("b")
for e := l.Front(); e != nil; e = e.Next() {
    fmt.Println(e.Value)
}

// ring (circulaire)
r := ring.New(3)
for i := 0; i < r.Len(); i++ {
    r.Value = i
    r = r.Next()
}

9.3 math

math.Max(10, 20)
math.Min(10, 20)
math.Abs(-10)
math.Sqrt(16)
math.Pow(2, 10)
math.Round(3.14159)
math.Floor(3.9)
math.Ceil(3.1)
math.Sin(math.Pi / 2)
math.Mod(10, 3)

9.4 crypto

// SHA-256
hash := sha256.Sum256([]byte("hello"))
fmt.Printf("%x\n", hash)

// HMAC
key := []byte("secret-key")
mac := hmac.New(sha256.New, key)
mac.Write([]byte("message"))
signature := mac.Sum(nil)

// AES
block, _ := aes.NewCipher([]byte("key-32-bytes-long"))
ciphertext := make([]byte, len(plaintext))
block.Encrypt(ciphertext, plaintext)

// Random sécurisé
token := make([]byte, 32)
rand.Read(token)
fmt.Printf("Token: %x\n", token)

10. Package encoding (XML, CSV)

10.1 XML

type Person struct {
    XMLName xml.Name `xml:"person"`
    ID      int      `xml:"id,attr"`
    Name    string   `xml:"name"`
    Age     int      `xml:"age"`
}

data := `<person id="1"><name>Alice</name><age>30</age></person>`
var p Person
xml.Unmarshal([]byte(data), &p)

10.2 CSV

f, _ := os.Create("data.csv")
w := csv.NewWriter(f)
w.Write([]string{"name", "age", "email"})
w.Write([]string{"Alice", "30", "alice@example.com"})
w.Write([]string{"Bob", "25", "bob@example.com"})
w.Flush()

f, _ = os.Open("data.csv")
r := csv.NewReader(f)
records, _ := r.ReadAll()
for _, record := range records {
    fmt.Println(record)
}

Résumé

  • fmt : formatage, Fprintf/Sscanf
  • io : Reader/Writer, Copy, bufio
  • strings/strconv : manipulation de strings
  • time : Time, Duration, Ticker, Timer
  • net/http : Client, Transport, middleware
  • encoding/json : Marshal/Unmarshal, tags, Encoder/Decoder
  • context : propagation, annulation, valeurs
  • reflect : introspection (usage modéré)
  • sort/container/math/crypto : utilitaires