Contexte et limites des interfaces Zig
Zig propose des interfaces comme mécanisme de polymorphisme d'exécution. Elles reposent sur des pointeurs opaques, des tables virtuelles et un dispatch dynamique. Cette approche garantit la conformité des signatures de méthodes, mais introduit une indirection qui empêche le compilateur d'appliquer les optimisations classiques de code monomorphe. En pratique, chaque appel via une interface implique un saut indirect, ce qui augmente le temps d'exécution et empêche le désenroulement de boucles ou l'inlining complet.
Duck typing à la compilation
Pour les sections critiques, les développeurs Zig utilisent le duck typing au moment de la compilation. Un paramètre de fonction peut être déclaré anytype ou être paramétré par un type T. Le compilateur effectue alors une réflexion sur T, vérifie la présence des méthodes attendues et génère du code monomorphisé, analogue aux templates C++ ou aux macros C. Cette méthode élimine le coût d'exécution du dispatch, mais transfère la charge vers le temps de compilation et nécessite que chaque violation de contrat soit détectée à la compilation.
Proposition de traits en Zig
L'article propose de formaliser les contrats compile‑temps sous forme de traits : des structures contenant uniquement des fonctions et des constantes liées à un type cible. Un trait est défini comme une fonction retournant un struct qui expose les éléments requis. Exemple :
fn Reducer(T: type) type {
return struct {
empty: T,
combine: fn (T, T) T,
fn reduce(self: @This(), xs: []const T) T {
var accum = self.empty;
for (xs) |x| accum = self.combine(accum, x);
return accum;
}
};
}
Le trait est ensuite implémenté pour un type concret :
const sum_trait = impl(Reducer(i32), struct {
pub const empty = 0;
pub fn combine(x: i32, y: i32) i32 { return x + y; }
});
Le compilateur inline la fonction reduce, déploie la boucle et élimine toute surcharge dynamique. Le modèle rappelle les vtables, mais la résolution se fait à la compilation, garantissant zéro coût d'exécution supplémentaire.
Analyse des impacts et contraintes
Les traits offrent plusieurs avantages : ils conservent la simplicité de Zig (aucune bibliothèque externe), permettent une autocomplétion fiable et assurent la vérification de contrat à la compilation. Cependant, ils imposent une duplication explicite de l'implémentation (impl) pour chaque type, ce qui peut entraîner une augmentation du temps de compilation et du volume binaire si de nombreux types sont instanciés. De plus, l'absence d'opérateur de surcharge oblige à écrire des fonctions comme add ou mul, ce qui peut rendre le code plus verbeux comparé à Rust ou Haskell. Enfin, le modèle ne fournit pas de mécanisme d'« inheritance » : chaque trait doit être déclaré séparément, ce qui limite la réutilisation transversale des comportements.
En résumé, les traits exploitent le point fort de Zig — le calcul à la compilation—pour proposer une alternative aux interfaces runtime, tout en conservant la philosophie de langage minimaliste. Leur adoption dépendra du compromis entre la rapidité d'exécution souhaitée et la charge supplémentaire imposée au processus de compilation.