# Interfaces Go Avancées en 2026 : Composition, Assertions de Type et Questions d'Entretien > Maîtriser la composition d'interfaces Go, les assertions de type et les génériques auto-référentiels de Go 1.26. Exemples pratiques et questions d'entretien technique. - Published: 2026-09-10 - Updated: 2026-09-10 - Author: Anthony Fillion-Maillet - Tags: go, interfaces, type-assertions, generics, go-1.26 - Reading time: 12 min --- Les interfaces Go définissent un comportement sans imposer d'implémentation, ce qui en fait un élément central pour écrire du code flexible et testable. Go 1.26 étend cette puissance avec les génériques auto-référentiels, les nouveaux itérateurs de réflexion et la gestion d'erreurs type-safe via `errors.AsType`. Ce guide couvre la composition d'interfaces, les assertions de type, les patterns d'embedding et les questions fréquentes en entretien technique. > **Amélioration des Interfaces Go 1.26** > > Les types génériques peuvent désormais se référencer eux-mêmes dans leur liste de paramètres de type : `type Adder[A Adder[A]] interface { Add(A) A }`. Ce pattern de polymorphisme F-borné permet des interfaces qui contraignent les types de retour au type implémentant lui-même. ## Composition d'Interfaces et Patterns d'Embedding L'embedding d'interfaces combine plusieurs interfaces en un seul contrat. Le résultat est une nouvelle interface qui requiert toutes les méthodes de ses interfaces embarquées. Ce pattern évite la duplication et crée des frontières d'abstraction claires. ```go // interfaces.go type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } // Composed interface embedding three interfaces type ReadWriteCloser interface { Reader Writer Closer } ``` Tout type implémentant les méthodes `Read`, `Write` et `Close` satisfait automatiquement `ReadWriteCloser`. La [bibliothèque standard Go](https://pkg.go.dev/io) utilise ce pattern de manière extensive dans le package `io`. Une question d'entretien courante demande aux candidats d'expliquer pourquoi Go préfère les interfaces petites et focalisées. La réponse réside dans la composabilité : `io.Reader` apparaît dans des centaines de fonctions car il ne demande qu'une seule méthode. Les interfaces plus grandes créent un couplage plus fort et réduisent la réutilisation. ## Assertions de Type : Syntaxe et Sécurité Les assertions de type extraient un type concret d'une valeur d'interface. La forme à deux valeurs empêche les panics en retournant un booléen indiquant le succès. ```go // assertions.go func processValue(v interface{}) { // Two-value assertion: safe, no panic if str, ok := v.(string); ok { fmt.Printf("String value: %s\n", str) return } // Type switch for multiple types switch val := v.(type) { case int: fmt.Printf("Integer: %d\n", val) case float64: fmt.Printf("Float: %.2f\n", val) case []byte: fmt.Printf("Bytes: %x\n", val) default: fmt.Printf("Unknown type: %T\n", val) } } ``` Les type switches gèrent proprement plusieurs types possibles. Chaque cas lie `val` au type asserté dans son bloc, éliminant le besoin d'assertions séparées. > **Risque de Panic** > > Les assertions à valeur unique comme `str := v.(string)` provoquent un panic si l'assertion échoue. Le code en production devrait toujours utiliser la forme à deux valeurs ou un type switch. Les recruteurs demandent souvent la performance des assertions de type. Le runtime effectue une seule comparaison de descripteurs de type, rendant les assertions peu coûteuses. Le coût augmente avec les valeurs d'interface qui encapsulent des pointeurs vers de grandes structs à cause de l'indirection, mais l'assertion elle-même reste O(1). ## Génériques Auto-Référentiels dans Go 1.26 Go 1.26 a introduit les paramètres de type auto-référentiels, permettant des interfaces où les méthodes doivent retourner le type implémentant. Ce pattern, parfois appelé [polymorphisme F-borné](https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification), résout une limitation de longue date. ```go // builder.go // Self-referential interface: methods return the same type type Builder[B Builder[B]] interface { WithName(name string) B WithAge(age int) B Build() string } type PersonBuilder struct { name string age int } func (p PersonBuilder) WithName(name string) PersonBuilder { p.name = name return p } func (p PersonBuilder) WithAge(age int) PersonBuilder { p.age = age return p } func (p PersonBuilder) Build() string { return fmt.Sprintf("%s, %d years old", p.name, p.age) } // Generic function using the self-referential constraint func configure[B Builder[B]](b B, name string, age int) string { return b.WithName(name).WithAge(age).Build() } ``` La contrainte `Builder[B]` garantit que `WithName` et `WithAge` retournent `B`, pas un `Builder` générique. Sans cela, le type de retour serait l'interface, perdant l'information de type concret et brisant le chaînage de méthodes. Les types mathématiques bénéficient de ce pattern. Une interface `Addable[A Addable[A]]` garantit que `Add(A) A` retourne le même type numérique, empêchant le mélange accidentel de valeurs `BigInt` et `Decimal`. ## errors.AsType : Unwrapping d'Erreurs Type-Safe Go 1.26 a ajouté [`errors.AsType`](https://pkg.go.dev/errors#AsType), remplaçant le pattern de dance de pointeurs de `errors.As`. La version générique retourne directement l'erreur unwrappée. ```go // errors_handling.go type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message) } func handleError(err error) { // Go 1.26: generic type-safe unwrapping if valErr, ok := errors.AsType[*ValidationError](err); ok { log.Printf("Validation error on field %s\n", valErr.Field) return } // Before Go 1.26: pointer-based unwrapping // var valErr *ValidationError // if errors.As(err, &valErr) { ... } log.Printf("Unexpected error: %v\n", err) } ``` La nouvelle API élimine la déclaration de variable séparée et rend le type cible explicite dans l'appel de fonction. Les chaînes d'erreurs sont recherchées de la même manière qu'avant ; seule l'interface a changé. ## Itérateurs de Réflexion pour l'Inspection d'Interfaces Go 1.26 a ajouté des méthodes itérateurs au package `reflect`. `Type.Methods()` et `Value.Methods()` retournent des itérateurs pour parcourir les méthodes, remplaçant les boucles basées sur les index. ```go // reflection.go import "reflect" func inspectInterface(v interface{}) { t := reflect.TypeOf(v) // Go 1.26: iterator-based method inspection fmt.Printf("Type %s methods:\n", t.Name()) for method := range t.Methods() { fmt.Printf(" %s: %s\n", method.Name, method.Type) } // For struct fields (also new in 1.26) if t.Kind() == reflect.Struct { for field := range t.Fields() { fmt.Printf(" Field: %s (%s)\n", field.Name, field.Type) } } } ``` Le pattern d'itérateur s'aligne avec la fonctionnalité range-over-function de Go 1.23. Le code devient plus lisible, et il n'y a pas de pénalité de performance : les itérateurs produisent les valeurs de manière lazy. ## Satisfaction d'Interface à la Compilation Go vérifie la satisfaction d'interface à la compilation lors de l'assignation d'une valeur concrète à une variable d'interface. Les vérifications explicites utilisant des assignations avec identifiant blanc détectent les erreurs tôt dans les grandes bases de code. ```go // compile_check.go type Storage interface { Save(key string, data []byte) error Load(key string) ([]byte, error) Delete(key string) error } type FileStorage struct { basePath string } // Compile-time check: fails if FileStorage misses a method var _ Storage = (*FileStorage)(nil) func (f *FileStorage) Save(key string, data []byte) error { path := filepath.Join(f.basePath, key) return os.WriteFile(path, data, 0644) } func (f *FileStorage) Load(key string) ([]byte, error) { path := filepath.Join(f.basePath, key) return os.ReadFile(path) } func (f *FileStorage) Delete(key string) error { return os.Remove(filepath.Join(f.basePath, key)) } ``` La ligne `var _ Storage = (*FileStorage)(nil)` compile uniquement si `*FileStorage` satisfait `Storage`. Ce pattern détecte les méthodes manquantes immédiatement plutôt qu'au runtime quand une valeur est assignée. ## L'Interface Vide et les Contraintes de Type L'interface vide `interface{}` accepte n'importe quelle valeur, tandis que `any` est son alias depuis Go 1.18. Les contraintes génériques fournissent une sécurité de type à la compilation sans assertions au runtime. ```go // constraints.go import "golang.org/x/exp/constraints" // Generic function with numeric constraint func Sum[T constraints.Integer | constraints.Float](values []T) T { var total T for _, v := range values { total += v } return total } // Comparable constraint for map keys func Contains[K comparable, V any](m map[K]V, key K) bool { _, exists := m[key] return exists } ``` Préférer les génériques à `interface{}` élimine les assertions de type et détecte les incompatibilités de type à la compilation. Le compromis est une complexité ajoutée dans les signatures de fonctions, donc les génériques conviennent mieux quand une fonction opère véritablement sur plusieurs types. ## Questions d'Entretien Technique sur les Interfaces Go Les recruteurs testent la connaissance des interfaces à plusieurs niveaux. Voici des questions courantes avec des réponses concises. **Q : Que se passe-t-il quand on appelle une méthode sur une valeur d'interface nil versus une valeur concrète nil à l'intérieur d'une interface ?** Une interface nil n'a ni type ni valeur ; appeler n'importe quelle méthode provoque un panic. Une interface non-nil contenant un pointeur nil a un type ; la méthode s'exécute avec un receiver nil. Ce comportement permet des patterns comme `(*bytes.Buffer)(nil).String()` retournant une chaîne vide. **Q : Comment fonctionne la comparaison d'interfaces ?** Deux valeurs d'interface sont égales si elles ont le même type dynamique et des valeurs dynamiques égales. Comparer des interfaces avec des types non comparables (slices, maps, fonctions) provoque un panic au runtime. **Q : Quand une méthode devrait-elle utiliser un receiver pointeur versus un receiver valeur ?** Les receivers pointeur permettent la mutation et évitent de copier les grandes structs. Les receivers valeur sont sûrs pour une utilisation concurrente et fonctionnent avec les valeurs et les pointeurs. Si une méthode a besoin d'un receiver pointeur, toutes les méthodes de ce type devraient utiliser des receivers pointeur pour la cohérence, puisque seul `*T` satisfait une interface requérant une méthode avec receiver pointeur. ```go // receiver_rules.go type Counter struct { count int } // Pointer receiver: modifies the struct func (c *Counter) Increment() { c.count++ } // Value receiver: read-only operation func (c Counter) Value() int { return c.count } type Incrementer interface { Increment() } func main() { var c Counter // var i Incrementer = c // Compile error: Counter lacks Increment var i Incrementer = &c // OK: *Counter has Increment i.Increment() } ``` **Q : Expliquer le principe « accepter des interfaces, retourner des structs ».** Les fonctions acceptant des interfaces se découplent des implémentations concrètes, permettant les tests avec des mocks. Retourner des types concrets donne aux appelants un accès complet aux méthodes du type sans assertions de type. Retourner une interface cache les futures additions de méthodes derrière une assertion de type. Pour plus de préparation aux entretiens Go, voir le module [questions sur la concurrence Go](/technologies/go/interview-questions/concurrency-patterns) et le [module testing](/technologies/go/interview-questions/testing). > **Conseil d'Entretien** > > Quand on demande de concevoir une interface, commencer par le cas à méthode unique. N'étendre que quand plusieurs méthodes sont toujours appelées ensemble. Les interfaces `Stringer`, `Reader` et `Handler` de la bibliothèque standard définissent chacune une seule méthode. ## Ce que les Développeurs Go Seniors Devraient Savoir sur les Interfaces - La composition d'interfaces par embedding crée des contrats flexibles sans hiérarchies d'héritage. Garder les interfaces petites : une à trois méthodes couvre la plupart des cas d'usage. - Les assertions de type et les type switches extraient les types concrets en toute sécurité. Toujours utiliser la forme à deux valeurs ou un switch en production pour éviter les panics. - Les génériques auto-référentiels de Go 1.26 permettent les patterns builder et les types mathématiques où les méthodes doivent retourner le type implémentant. - `errors.AsType` simplifie l'unwrapping d'erreurs avec une API générique et type-safe. L'utiliser dans le nouveau code ciblant Go 1.26 ou ultérieur. - Les vérifications d'interface à la compilation avec `var _ Interface = (*Type)(nil)` détectent les méthodes manquantes avant l'exécution des tests. - Préférer les génériques à `interface{}` quand la sécurité de type à la compilation l'emporte sur la complexité des signatures. - La directive « accepter des interfaces, retourner des structs » garde les APIs flexibles pour les appelants tout en exposant toutes les fonctionnalités. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/go/go-interfaces-advanced-composition-type-assertions-2026