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.

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.
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.
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 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.
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.
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é, résout une limitation de longue date.
// 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, remplaçant le pattern de dance de pointeurs de errors.As. La version générique retourne directement l'erreur unwrappée.
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é.
Prêt à réussir tes entretiens Go ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
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.
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.
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.
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.
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 et le module testing.
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.AsTypesimplifie 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.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tu saurais repérer le bug en Go ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 10 septembre 2026
Tags
Partager
Articles similaires

Questions d'entretien Go 1.26 : Green Tea GC, go fix et nouvelles fonctionnalités
Guide complet des questions d'entretien sur Go 1.26 couvrant le nouveau garbage collector Green Tea, les optimisations de pile, l'outil go fix et les améliorations syntaxiques.

Le package context en Go en 2026 : Annulation, Timeouts et Questions d'Entretien
Guide complet sur le package context de Go : gestion de l'annulation, des timeouts et des deadlines. Inclut les questions fréquentes en entretien technique et les bonnes pratiques de production.

Go SIMD et le Package ArchSIMD en 2026 : Optimisation des Performances et Questions d'Entretien
Découvrez le package simd/archsimd de Go 1.26 : opérations vectorielles natives, optimisation des performances et préparation aux entretiens techniques Go.