Nouvelle version de Go : 1.18
Tags: go, french
Published: 2022-03-25 21:17:30

## Generics

On peut définir la programmation générique comme un style de programmation qui permet de représenter des fonctions et des structures de données sous une forme générique, avec des types adaptés. Ca, c’est pour la théorie. Voyons un exemple.

Pour illustrer le principe, imaginons que nous avons besoins de calculer la somme des éléments d’un `map` de `int64` :

```go
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}
```

Rien d’exceptionnel.

Maintenant, si nous avons besoin de faire de même pour un `map` de `float64`, notre fonction pourrait être quelque chose de ce genre :

```go
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}
```

On constate la très grande similarité entre nos deux fonctions ; rien ne change à part le type.

### Déclarer une fonction générique

Pour pouvoir utiliser des valeurs de plusieurs types, il faut écrire une fonction qui déclare des `type parameters` en plus des paramètres de fonction habituels.

Chaque `type parameter` a une contrainte qui permet de spécifier le type d'argument qui peut être accepté par la fonction. 

Notre fonction générique peut se définir ainsi :

```go
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}
```

On peut déclarer les contraintes comme une interface et modifier la fonction en conséquence :

```go
type Number interface {
    int64 | float64
}

// SumNumbers sums the values of map m. It supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}
```

### Cas d’usage

La généricité peut être utile pour les fonctions qui font :

- Trouver le plus petit/grand élément d’un `slice`
- Trouver la déviation moyenne/standard d’un `slice`
- Calculer l’union/intersection de `maps`
- Trouver le chemin le plus court d’un nœud d’un graphe
- Appliquer une fonction de transformation à un `slice`/`map`, qui retourne un `slice` de résultat

Dans les cas plus spécifique à l’utilisation de la concurrence en **Go**, on pourrait avoir les cas d’usages suivant :

- Lire un canal avec un *timeout*
- Combiner deux canaux en un seul
- Appeler une liste de fonctions en parallèle, renvoyer un `slice` de résultat
- Appeler une liste de fonctions, en utilisant un *Context*, retourner le résultat de la première fonction qui termine, annuler et nettoyer les autres *goroutines*

### Aller plus loin

Les exemples et les explications sont tirés [de ce tutoriel]([https://go.dev/doc/tutorial/generics](https://go.dev/doc/tutorial/generics)) (et également plus complet).

Une [introduction publiée sur le blog officiel](https://go.dev/blog/intro-generics) de Go.

Les détails sont dans la [release note]([https://go.dev/doc/go1.18#generics](https://go.dev/doc/go1.18#generics)). 

Des explications sur le pourquoi, les cas d’usages classiques : [Why Generics ?]([https://go.dev/blog/why-generics](https://go.dev/blog/why-generics)).

## Fuzzing

Le *fuzzing* est l’injection de données aléatoires dans un test afin de tenter de trouver une vulnérabilités ou de détecter des potentielles entrées qui pourraient faire *crasher* un programme. Voyons un exemple de mise en œuvre.

### Exemple

On prend une fonction qui inverse le sens d’une chaîne de caractères :

```go
func Reverse(s string) string {
    b := []byte(s)
    for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return string(b)
}
```

Cette fonction prend une `string` en entrée, itère octet (`byte`) par octet et renvoi la `string` inversée.

*Nota*: ce code est basé sur la fonction `stringutil.Reverse` de `golang.org/x/example`.

Ajoutons un test unitaire :

```go
package main

import (
    "testing"
)

func TestReverse(t *testing.T) {
    testcases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {" ", " "},
        {"!12345", "54321!"},
    }
    for _, tc := range testcases {
        rev := Reverse(tc.in)
        if rev != tc.want {
                t.Errorf("Reverse: %q, want %q", rev, tc.want)
        }
    }
}
```

Il s’agit d’un simple test qui s’assure que la `string` d'entrée est bien inversée.

Remplaçons maintenant ce test classique par un test en *fuzzing* :

```go
func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc)  // Use f.Add to provide a seed corpus
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        if orig != doubleRev {
            t.Errorf("Before: %q, after: %q", orig, doubleRev)
        }
        if utf8.ValidString(orig) && !utf8.ValidString(rev) {
            t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
        }
    })
}
```

Il y a des différences de syntaxes par rapport aux tests classiques :

- La nom de la fonction débute par `FuzzXxx` (à la place de `TestXxx)`
- La fonction prend `*testing.F` en type d'entrée (à la place de `*testing.T`)
- On utilise `f.Fuzz` (à la place de `t.Run`) qui prend comme paramètre une fonction à *fuzzer* qui a comme paramètres `*testing.T` et le type à *fuzzer*

On peut ici identifier une limitation à ce type de test : il n’est pas possible de prédire la sortie puisque nous contrôlons pas l’entrée. Il faut donc s’appuyer sur d’autres propriétés pour réaliser nos tests, comme :

- Inverser deux fois l’entrée doit préserver l’entrée originale
- La chaîne inversée doit être une chaîne UTF-8 valide

### Aller plus loin

Le [tutoriel de go.dev]([https://go.dev/doc/tutorial/fuzz](https://go.dev/doc/tutorial/fuzz)) (dont sont tiré les exemples ci-dessus), ainsi que la [documentation officielle]([https://go.dev/doc/fuzz/](https://go.dev/doc/fuzz/)) vous permettrons de creuser le sujet.

## Workspaces

Cette nouvelle fonctionnalité permet de simplifier le travail sur de multiples paquets interdépendant en même temps.

Un *workspace* est défini par un fichier `go.work` de cette forme :

```go
go 1.18

use (
  ../foo/bar
  ./baz
)

replace example.com/foo v1.2.3 => example.com/bar v1.4.5
```

On peut alors initialiser un espace de travail dans le répertoire courant :

```bash
go work init
```

### Aller plus loin

- [Workspaces in Go 1.18]([https://zerokspot.com/weblog/2022/01/14/workspaces-in-go118/](https://zerokspot.com/weblog/2022/01/14/workspaces-in-go118/))
- La [documentation officielle](https://pkg.go.dev/cmd/go@master#hdr-Workspace_maintenance)
- Une [vidéo Youtube de Michael Matloob](https://www.youtube.com/watch?v=wQglU5aB5NQ)

## Autres ajouts

- Amélioration des performances de 20% sur *Apple M1*, *ARM64*, *PowerPC64.*
- Le type `any` comme alias de `interface{}` : l’ [`issue`](https://github.com/golang/go/issues/49884) sur *Github*,  un [article de blog](https://blog.carlmjohnson.net/post/2022/golang-118-even-more-minor-features/).
- Un nouveau paquet `net/netip` avec un type d’adresse IP : [documentation officielle](https://pkg.go.dev/net/netip).
- Un paquet `debug/buildinfo` pour accéder aux informations d’un binaire compilé : [documentation officielle](https://pkg.go.dev/debug/buildinfo).

### Pour conclure

Voilà pour ce qui me paraît l’essentiel à savoir sur cette nouvelle version de Go. Vous trouverez l’intégralité des ajouts, des suppressions et des corrections dans la *[Release Note](https://go.dev/doc/go1.18)*.

Codez bien !