---
title: "?Sized : une seule fonction pour les types sized et unsized"
description: "Comprendre le rôle des bounds ?Sized dans les définitions de traits Rust et les exploiter pour créer des fonctions flexibles qui fonctionnent efficacement avec des types sized et unsized"
url: "https://mayorana.ch/fr/blog/sized-unsized-bounds-flexibility"
language: "fr"
date: "2025-12-03"
author: "Mohamed Bennekrouf"
tags:
  - "rust"
  - "sized"
  - "unsized"
  - "bounds"
  - "traits"
  - "generics"
en_version: "https://mayorana.ch/en/blog/sized-unsized-bounds-flexibility.md"
---

# ?Sized : une seule fonction pour les types sized et unsized

Le bound `?Sized` dans les définitions de traits Rust relâche la contrainte `Sized` par défaut sur les types génériques, permettant à une fonction ou trait de fonctionner avec à la fois des types sized (taille connue pendant la compilation, comme `[u8; 16]`) et des types unsized (ex : `[u8]`, `str`, `dyn Trait`). Dans une bibliothèque de sérialisation de données, j'utiliserais `?Sized` pour écrire une fonction flexible qui traite à la fois les tableaux fixes et les slices dynamiques efficacement, améliorant la fonctionnalité sans sacrifier la performance.

<div class="svg-container" style="margin:2rem 0;">
<svg class="td6-fig" viewBox="0 0 800 260" width="100%" style="height:auto;max-width:780px;display:block;margin:0 auto;" role="img" aria-label="Entrées sized et unsized convergeant via un bound T: ?Sized + Checksum vers une seule fonction compute_checksum">
<style>
.td6-fig{--bg:#f8fafc;--box:#ffffff;--tx:#1e293b;--mut:#64748b;--ln:#cbd5e1;--ac:#FF6B00}
:root.dark .td6-fig,[data-theme="dark"] .td6-fig{--bg:#0f172a;--box:#1e293b;--tx:#f8fafc;--mut:#94a3b8;--ln:#475569}
.td6-fig .box{fill:var(--box);stroke:var(--ln);stroke-width:1.5}
.td6-fig .boxAc{fill:var(--box);stroke:var(--ac);stroke-width:2}
.td6-fig .tx{fill:var(--tx);font:600 12px ui-sans-serif,system-ui,sans-serif;text-anchor:middle}
.td6-fig .ti{fill:var(--tx);font:700 14px ui-sans-serif,system-ui,sans-serif;text-anchor:middle}
.td6-fig .mut{fill:var(--mut);font:600 11px ui-sans-serif,system-ui,sans-serif;text-anchor:middle}
.td6-fig .ln{stroke:var(--ln);stroke-width:1.5;fill:none}
.td6-fig .lnAc{stroke:var(--ac);stroke-width:2;fill:none}
</style>
<!-- markers -->
<defs>
<marker id="td6-arrow-fr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0L10,5L0,10z" fill="var(--ln)"/></marker>
<marker id="td6-arrowAc-fr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0L10,5L0,10z" fill="var(--ac)"/></marker>
</defs>
<!-- sized input -->
<rect class="box" x="40" y="30" width="260" height="50" rx="6"/>
<text x="170" y="52" class="tx">FixedBuffer([u8; 16])</text>
<text x="170" y="68" class="mut">Sized — connu à la compilation</text>
<!-- unsized input -->
<rect class="box" x="40" y="110" width="260" height="50" rx="6"/>
<text x="170" y="132" class="tx">&amp;[u8] slice</text>
<text x="170" y="148" class="mut">Unsized — fat pointer (data+len)</text>
<!-- Y-merge to shared point then single arrow -->
<path class="ln" d="M300,55 L340,55 L340,105" marker-end="none"/>
<path class="ln" d="M300,135 L340,135 L340,105" marker-end="none"/>
<path class="lnAc" d="M340,105 L390,105" marker-end="url(#td6-arrowAc-fr)"/>
<!-- function box -->
<rect class="boxAc" x="392" y="75" width="330" height="60" rx="6"/>
<text x="557" y="98" class="tx">compute_checksum&lt;T: ?Sized + Checksum&gt;</text>
<text x="557" y="116" class="mut">data: &amp;T</text>
<!-- arrow to result -->
<path class="ln" d="M557,135 L557,161" marker-end="url(#td6-arrow-fr)"/>
<rect class="box" x="437" y="162" width="240" height="42" rx="6"/>
<text x="557" y="188" class="tx">checksum() — inliné, sans overhead</text>
<!-- caption -->
<text x="400" y="230" class="mut">?Sized relâche la contrainte Sized par défaut, une seule fonction accepte les deux formes</text>
</svg>
</div>

## Rôle de ?Sized

- **Sized par Défaut** : Par défaut, les paramètres génériques (`T`) impliquent `T: Sized`, signifiant que la taille du type doit être connue pendant la compilation. Cela exclut les types unsized comme les slices (`[u8]`), chaînes (`str`), ou trait objects (`dyn Trait`), qui n'existent que derrière des pointeurs (ex : `&[u8]`, `Box<dyn Trait>`).
- **Signification de ?Sized** : Ajouter `T: ?Sized` se retire de cette exigence, permettant à `T` d'être soit sized soit unsized. Cela active une applicabilité plus large, car la fonction peut accepter des références vers des types unsized (`&T`) ou des types sized directement.

## Exemple : fonction de Sérialisation

Dans une bibliothèque de sérialisation, je définirais une fonction pour calculer une checksum sur n'importe quelles données contiguës de type byte :

```rust
trait Checksum {
    fn checksum(&self) -> u32;
}

fn compute_checksum<T: ?Sized + Checksum>(data: &T) -> u32 {
    data.checksum()
}

// Implémentations
struct FixedBuffer([u8; 16]);
struct DynamicBuffer([u8]);

impl Checksum for FixedBuffer {
    fn checksum(&self) -> u32 {
        self.0.iter().fold(0, |acc, &x| acc.wrapping_add(x as u32))
    }
}

impl Checksum for [u8] { // Type unsized
    fn checksum(&self) -> u32 {
        self.iter().fold(0, |acc, &x| acc.wrapping_add(x as u32))
    }
}

// Usage
let fixed = FixedBuffer([1; 16]);
let dynamic = vec![2; 32];
let fixed_sum = compute_checksum(&fixed);        // Sized: [u8; 16]
let dynamic_sum = compute_checksum(&dynamic[..]); // Unsized: [u8]
```

## Comment ?Sized Améliore la Fonctionnalité

### Flexibilité
Sans `?Sized`, `compute_checksum` rejetterait `&[u8]` :

```rust
fn compute_checksum<T: Sized + Checksum>(data: &T) -> u32 { /* ... */ }
// Erreur : [u8] n'implémente pas Sized
```

Avec `T: ?Sized`, elle accepte :
- **Sized** : `FixedBuffer` (16 octets connus pendant la compilation).
- **Unsized** : `[u8]` (taille connue seulement à l'exécution via la longueur).

### API Unifiée
Une fonction gère à la fois les tableaux fixes (`[u8; 16]`) et les slices (`[u8]`), plus les [trait objects](https://mayorana.ch/fr/blog/object-safety-rust) (`dyn Checksum`) si nécessaire. Cela réduit la duplication de code dans une bibliothèque de sérialisation traitant des entrées diverses.

## Maintenir l'Efficacité

- **Basé sur Références** : Utiliser `&T` évite de posséder `T` ou de nécessiter `Box<T>`. Pour les types unsized, cela exploite leur indirection inhérente (ex : `&[u8]` est un fat pointer : données + longueur), n'ajoutant aucun coût supplémentaire.
- **[Dispatch Statique](https://mayorana.ch/fr/blog/dispatch-performance-rust)** : `T: ?Sized + Checksum` assure la monomorphization pour chaque `T`. Les appels `checksum` sont inlinés :
  - Pour `FixedBuffer` : Accès direct au tableau, unrollé si petit.
  - Pour `[u8]` : Itération de slice, potentiellement vectorisée par LLVM.
- **Pas d'Overhead** : Le bound `?Sized` lui-même n'ajoute aucun coût à l'exécution—c'est un relâchement pendant la compilation. La vtable (si `dyn Checksum`) n'est utilisée que si explicitement choisie, pas ici.

<div class="svg-container" style="margin:2rem 0;">
<svg class="td6b-fig" viewBox="0 0 800 310" width="100%" style="height:auto;max-width:780px;display:block;margin:0 auto;" role="img" aria-label="Disposition en mots d'une référence vers un buffer sized, une slice et un trait object, montrant pointeurs fins et fat pointers">
<style>
.td6b-fig{--bg:#f8fafc;--box:#ffffff;--tx:#1e293b;--mut:#64748b;--ln:#cbd5e1;--ac:#FF6B00}
:root.dark .td6b-fig,[data-theme="dark"] .td6b-fig{--bg:#0f172a;--box:#1e293b;--tx:#f8fafc;--mut:#94a3b8;--ln:#475569}
.td6b-fig .box{fill:var(--box);stroke:var(--ln);stroke-width:1.5}
.td6b-fig .boxAc{fill:var(--box);stroke:var(--ac);stroke-width:2}
.td6b-fig .tx{fill:var(--tx);font:600 12px ui-sans-serif,system-ui,sans-serif;text-anchor:middle}
.td6b-fig .ti{fill:var(--tx);font:700 14px ui-sans-serif,system-ui,sans-serif;text-anchor:middle}
.td6b-fig .mut{fill:var(--mut);font:600 11px ui-sans-serif,system-ui,sans-serif;text-anchor:middle}
.td6b-fig .ln{stroke:var(--ln);stroke-width:1.5;fill:none}
</style>
<!-- title -->
<text x="400" y="26" class="ti">De quoi &amp;T est fait, une ligne par T</text>
<!-- column headers -->
<text x="140" y="70" class="mut">la référence</text>
<text x="325" y="70" class="mut">mot 0</text>
<text x="485" y="70" class="mut">mot 1</text>
<text x="680" y="70" class="mut">largeur, et où vit la taille</text>
<!-- row 1: sized -->
<rect class="box" x="40" y="84" width="200" height="52" rx="6"/>
<text x="140" y="106" class="tx">&amp;FixedBuffer</text>
<text x="140" y="124" class="mut">Sized</text>
<rect class="box" x="250" y="84" width="150" height="52" rx="6"/>
<text x="325" y="115" class="tx">ptr données</text>
<text x="485" y="115" class="mut">— pas de second mot —</text>
<text x="680" y="106" class="tx">8 octets</text>
<text x="680" y="124" class="mut">taille inscrite dans [u8; 16]</text>
<!-- row 2: slice -->
<rect class="box" x="40" y="146" width="200" height="52" rx="6"/>
<text x="140" y="168" class="tx">&amp;[u8]</text>
<text x="140" y="186" class="mut">slice unsized</text>
<rect class="box" x="250" y="146" width="150" height="52" rx="6"/>
<text x="325" y="177" class="tx">ptr données</text>
<rect class="box" x="410" y="146" width="150" height="52" rx="6"/>
<text x="485" y="177" class="tx">len: usize</text>
<text x="680" y="168" class="tx">16 octets</text>
<text x="680" y="186" class="mut">la longueur voyage dans le pointeur</text>
<!-- row 3: trait object -->
<rect class="box" x="40" y="208" width="200" height="52" rx="6"/>
<text x="140" y="230" class="tx">&amp;dyn Checksum</text>
<text x="140" y="248" class="mut">trait object</text>
<rect class="box" x="250" y="208" width="150" height="52" rx="6"/>
<text x="325" y="239" class="tx">ptr données</text>
<rect class="boxAc" x="410" y="208" width="150" height="52" rx="6"/>
<text x="485" y="239" class="tx">ptr vtable</text>
<text x="680" y="230" class="tx">16 octets</text>
<text x="680" y="248" class="mut">taille et checksum() dans la vtable</text>
<!-- caption -->
<text x="400" y="290" class="mut">?Sized admet tout T dont la taille voyage dans le pointeur plutôt que dans le type — &amp;T ne coûte rien de plus</text>
</svg>
</div>

## Détails d'Implémentation

- **[Trait Bound](https://mayorana.ch/fr/blog/trait-bounds-rust)** : `Checksum` définit le comportement, implémenté pour les types sized (`FixedBuffer`) et unsized (`[u8]`). `?Sized` permet à `compute_checksum` de les relier.
- **Sécurité** : `&T` assure la sémantique de borrowing, prévenant les problèmes d'ownership avec les types unsized (qui ne peuvent pas être déplacés directement).

## Compromis

- **Indirection** : Les types unsized nécessitent une référence ou smart pointer (`&T`, `Box<T>`), ajoutant une couche vs `T` direct pour les types sized. Dans un chemin chaud, cela pourrait compter (ex : pointer chasing).
- **Complexité** : Les appelants doivent comprendre `&T` vs `T`. Je documenterais que `compute_checksum` prend des références pour l'universalité.
- **Alternative** : Si seules les slices sont nécessaires, `&[u8]` directement pourrait suffire, mais `?Sized` supporte un usage plus large (ex : `dyn Trait`).

## Confirmer que les deux cas passent
### Test de compilation
S'assurer que les types sized et unsized fonctionnent :

```rust
assert_eq!(compute_checksum(&FixedBuffer([1; 16])), 16);
assert_eq!(compute_checksum(&vec![2; 32][..]), 64);
```

### Benchmark
Utilise criterion pour vérifier l'overhead :

```rust
use criterion::{black_box, Criterion};
fn bench(c: &mut Criterion) {
    let fixed = FixedBuffer([1; 16]);
    let dynamic = vec![2; 32];
    c.bench_function("fixed", |b| b.iter(|| compute_checksum(black_box(&fixed))));
    c.bench_function("dynamic", |b| b.iter(|| compute_checksum(black_box(&dynamic[..]))));
}
```

Attends-toi à des performances similaires aux appels directs, avec inlining.

## Exemple avancé : Support de Trait Objects

Pour étendre encore plus la flexibilité :

```rust
// Permet aussi les trait objects
fn compute_any_checksum(data: &dyn Checksum) -> u32 {
    data.checksum()
}

// Utilisation avec dispatch dynamique
let items: Vec<Box<dyn Checksum>> = vec![
    Box::new(FixedBuffer([1; 16])),
    Box::new(vec![2; 8]),
];

for item in &items {
    println!("Checksum: {}", compute_any_checksum(item.as_ref()));
}
```

## Considérations pratiques

### Quand utiliser ?Sized

**Utilise `?Sized` quand :**
- Tu veux une API unifiée pour types sized et unsized
- Tu travailles avec des slices, strings, ou trait objects
- Tu créés des abstractions génériques flexibles

**Évite `?Sized` quand :**
- Tu n'as besoin que de types sized
- La performance est critique et l'indirection pose problème
- La complexité supplémentaire n'apporte pas de valeur

### Patterns courants

```rust
// Pattern 1: Fonction générique flexible
fn process<T: ?Sized + MyTrait>(data: &T) { /* ... */ }

// Pattern 2: Méthode sur trait avec ?Sized
trait Processor {
    fn process<T: ?Sized + Display>(&self, item: &T);
}

// Pattern 3: Structure générique avec ?Sized
struct Wrapper<T: ?Sized> {
    inner: Box<T>,
}
```

## `?Sized` en pratique
`?Sized` permet à `compute_checksum` de gérer à la fois les types sized et unsized en relâchant la contrainte `Sized`, ce qui en fait l'idéal pour une bibliothèque de sérialisation. Elle maintient l'efficacité via le dispatch statique et les références, offrant la flexibilité sans coût à l'exécution. J'utiliserais cela pour unifier les APIs à travers des types de données divers, assurant performance et scalabilité dans un système Rust.
