Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
FondamentauxIntermédiaireSérie : Fondamentaux

Typage structurel vs nominal

Comment un langage décide si deux types sont compatibles : par leur forme (structurel, comme TypeScript) ou par leur nom déclaré (nominal, comme C# ou Java). Une différence subtile aux grosses conséquences.

Par Baptiste Vidal
2 min de lecture
Mis à jour il y a 2 semaines
fondamentauxtypagestructurelnominaltypescript

Typage structurel vs nominal

Une fois qu'on sait quand un langage vérifie les types, reste une autre question, plus subtile : comment décide-t-il que deux types sont compatibles ? Deux écoles s'opposent - et c'est une vraie surprise quand on passe de C# à TypeScript.


# La question : « ce type en accepte-t-il un autre ? »

Quand tu passes un objet à une fonction qui attend un certain type, le langage doit décider : est-ce compatible ? Il y a deux façons radicalement différentes de répondre.

  • Nominal : compatible si les types portent le même nom ou ont une relation déclarée (héritage, implémentation d'interface).
  • Structurel : compatible s'ils ont la même forme (les mêmes champs, les mêmes méthodes) - peu importe leur nom.

# Typage nominal (C#, Java)

Ce qui compte, c'est l'identité déclarée du type. Deux types aux champs identiques restent incompatibles tant qu'on n'a pas explicitement déclaré un lien entre eux.

class Point2D  { public int X; public int Y; }
class Vecteur2D { public int X; public int Y; }
 
Point2D p = new Vecteur2D(); // ❌ Erreur : ce sont deux types distincts,
                             //    malgré des champs identiques

Pour qu'un Vecteur2D soit accepté là où on attend un Point2D, il faudrait qu'il hérite de Point2D ou implémente une interface commune. Le nom et la filiation font foi.


# Typage structurel (TypeScript, Go)

Ce qui compte, c'est la forme. Si un objet a tout ce que le type attend, il est accepté - même s'il n'a jamais « déclaré » quoi que ce soit.

interface Point {
  x: number;
  y: number;
}
 
function afficher(p: Point) {
  console.log(p.x, p.y);
}
 
const v = { x: 10, y: 20, z: 5 }; // un simple objet, aucune déclaration
afficher(v); // ✅ accepté : il a bien x et y, la forme correspond

v n'implémente rien du tout, mais il a la bonne forme : TypeScript le considère comme un Point valide. C'est ce qu'on résume par « si ça a la bonne forme, ça passe ».


# Les conséquences

Nominal (C#, Java)Structurel (TS, Go)
CompatibilitéPar nom / relation déclaréePar forme (les membres)
SouplesseStricte : il faut déclarer les liensGrande : pas de boilerplate
RisqueVerbeuxCompatibilités « accidentelles »

Le structurel évite beaucoup de cérémonie (pas besoin de déclarer « j'implémente telle interface »), au prix de compatibilités parfois involontaires. Le nominal est plus strict : deux choses ne sont interchangeables que si tu l'as explicitement voulu.


# À retenir

  • La question : comment décide-t-on que deux types sont compatibles ?
  • Nominal (C#, Java) : par le nom et les relations déclarées (héritage, interfaces).
  • Structurel (TypeScript, Go) : par la forme - mêmes membres = compatibles.
  • Le structurel est plus souple mais peut créer des compatibilités accidentelles.

À lire ensuite : Le duck typing, le cousin dynamique du typage structurel.