Un atelier Rust · Édition d'ApprentissageA Rust Workshop · Learning Edition

Trait objects (dyn), ou la vtable et son prix Trait objects (dyn), or the vtable and its price

Une vtable, un coût d'indirection, échangé contre la flexibilité : mettre des types hétérogènes dans le même Vec. Contre-exemple : Box<dyn> réflexe alors qu'un generic suffisait. A vtable, an indirection cost, traded for flexibility: putting heterogeneous types in the same Vec. Counter-example: reflexive Box<dyn> when a generic would do.

AudienceAudience
Dev maîtrisant traits et generics (Vol 5 №1) Dev fluent in traits and generics (Vol 5 №1)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ Traitsdynvtable

Chapitre 1 en accès libre — la suite (ch. 2 à 5) est réservée. Chapter 1 free to read — the rest (ch. 2–5) is members-only.

01CadrageFraming3 min

Quand le compilateur ne peut pas connaître le type, on l'efface.When the compiler can't know the type, you erase it.

Le numéro précédent a montré la moitié lumineuse du dispatch : les generics, résolus à la compilation, à coût nul. Mais cette résolution suppose que le compilateur connaisse le type concret de chaque appel. Parfois, c'est impossible — non par paresse, mais par nature. Un Vec<T> générique est homogène : un seul T pour tout le vecteur. Comment mettre un cercle et un rectangle dans la même collection, les traiter uniformément, alors qu'ils n'ont pas le même type ? La réponse est le trait object : dyn Surface, un type « effacé » qui ne dit plus que « ceci honore le contrat Surface ». On échange la connaissance du type concret — et le coût zéro qui allait avec — contre la liberté de mélanger.The previous issue showed dispatch's bright half: generics, resolved at compile time, at zero cost. But that resolution assumes the compiler knows each call's concrete type. Sometimes that's impossible — not from laziness, but by nature. A generic Vec<T> is homogeneous: a single T for the whole vector. How do you put a circle and a rectangle in the same collection, handle them uniformly, when they aren't the same type? The answer is the trait object: dyn Surface, an 'erased' type that says only 'this honors the Surface contract'. You trade knowing the concrete type — and the zero cost that came with it — for the freedom to mix.

dyn Surface — des types différents, une collection, un traitementdyn Surface — different types, one collection, one treatment
// Le générique fige UN type par usage : un Vec<T> ne contient qu'un seul T.
// Pour mélanger des types qui partagent un trait, on EFFACE le type concret :

let formes: Vec<Box<dyn Surface>> = vec![
    Box::new(Cercle { r: 1.0 }),       // un Cercle…
    Box::new(Rectangle { l: 2.0, h: 3.0 }),  // …et un Rectangle, dans le MÊME Vec
];

for f in &formes {
    println!("aire = {}", f.aire());   // l'appel trouve la bonne méthode à l'exécution
}
// dyn Surface = « un quelque-chose qui est Surface, dont j'ai oublié le type exact ».
Effacer le type, garder le contratErase the type, keep the contract

C'est le renversement exact du générique. <T: Surface> dit « un type précis, choisi au site d'appel, que je connais à la compilation ». dyn Surface dit « n'importe quel type qui est Surface, et je ne saurai lequel qu'à l'exécution ». Le premier permet la monomorphisation et interdit l'hétérogénéité ; le second permet l'hétérogénéité et interdit la monomorphisation. Ce ne sont pas deux outils concurrents pour le même problème : ce sont les deux réponses à deux questions opposées — « un type, connu » contre « plusieurs types, ou inconnu ».It's the exact reversal of the generic. <T: Surface> says 'a precise type, chosen at the call site, that I know at compile time'. dyn Surface says 'any type that is Surface, and I won't know which until runtime'. The first enables monomorphization and forbids heterogeneity; the second enables heterogeneity and forbids monomorphization. They aren't two competing tools for the same problem: they're the two answers to two opposite questions — 'one type, known' versus 'many types, or unknown'.

Le réflexe du numéroThe issue's reflex

« À ce point du code, le type concret est-il unique et connu, ou multiple/inconnu ? » Unique et connu → générique, dispatch statique, coût zéro. Plusieurs types à mélanger, ou un type décidé à l'exécution (plugin, configuration, entrée) → dyn. Le besoin d'hétérogénéité, ou d'un type différé, est le signal qui fait basculer du statique vers le dynamique.'At this point in the code, is the concrete type unique and known, or multiple/unknown?' Unique and known → generic, static dispatch, zero cost. Several types to mix, or a type decided at runtime (plugin, configuration, input) → dyn. The need for heterogeneity, or a deferred type, is the signal that tips you from static to dynamic.

Pourquoi Box ?Why Box?

Un dyn Surface n'a pas de taille connue à la compilation : un Cercle et un Rectangle n'occupent pas le même nombre d'octets, et le type est effacé. Or Rust doit connaître la taille de ce qu'il pose sur la pile. On range donc la valeur derrière un pointeur — Box<dyn Surface> (sur le tas), ou &dyn Surface (emprunt). Le pointeur, lui, a une taille fixe. Le Vol 6 reviendra en détail sur Box ; ici, il sert juste à donner une taille à l'inconnu.A dyn Surface has no compile-time size: a Cercle and a Rectangle don't occupy the same number of bytes, and the type is erased. Yet Rust must know the size of what it puts on the stack. So you store the value behind a pointer — Box<dyn Surface> (on the heap), or &dyn Surface (a borrow). The pointer itself has a fixed size. Vol 6 will return to Box in detail; here, it just gives a size to the unknown.

🔒

La suite est réservée The rest is members-only

Le premier numéro est libre. Débloque tout The Rust Loop — tous les volumes, à vie — pour 5 €, paiement unique. The first issue is free. Unlock all of The Rust Loop — every volume, forever — for €5, one-time.

Retour au kiosqueBack to newsstand