---
title: "Comment stocker une closure dans une struct ?"
description: "Stocker des closures dans des structs en utilisant des paramètres génériques, trait objects, et annotations de lifetime avec les bounds Fn, FnMut, et FnOnce"
url: "https://mayorana.ch/fr/blog/storing-closures-in-structs-fr"
language: "fr"
date: "2025-11-11"
author: "Mohamed Bennekrouf"
tags:
  - "rust"
  - "closures"
  - "structs"
  - "trait-bounds"
  - "lifetimes"
  - "generic-types"
en_version: "https://mayorana.ch/en/blog/storing-closures-in-structs.md"
---

# Comment stocker une closure dans une struct ?

Stocker une closure dans une struct nécessite de spécifier des [trait bounds](https://mayorana.ch/fr/blog/trait-bounds-rust) (`Fn`, `FnMut`, `FnOnce`) et potentiellement des lifetimes si la closure capture des références. Voici comment faire :

<div class="svg-container" style="margin:2rem 0;">
<svg class="cl9-fig" viewBox="0 0 800 280" width="100%" style="height:auto;max-width:780px;display:block;margin:0 auto;" role="img" aria-label="Trois façons de stocker une closure dans une struct : dispatch statique générique, dispatch dynamique boxé, ou références liées à une lifetime">
<!-- style -->
<style>
.cl9-fig{--bg:#f8fafc;--box:#ffffff;--tx:#1e293b;--mut:#64748b;--ln:#cbd5e1;--ac:#FF6B00}
:root.dark .cl9-fig,[data-theme="dark"] .cl9-fig{--bg:#0f172a;--box:#1e293b;--tx:#f8fafc;--mut:#94a3b8;--ln:#475569}
.cl9-fig .box{fill:var(--box);stroke:var(--ln);stroke-width:1.5}
.cl9-fig .boxac{fill:var(--box);stroke:var(--ac);stroke-width:2}
.cl9-fig .ti{fill:var(--tx);font:700 14px ui-sans-serif,system-ui,sans-serif}
.cl9-fig .tx{fill:var(--tx);font:600 12px ui-sans-serif,system-ui,sans-serif}
.cl9-fig .mut{fill:var(--mut);font:11px ui-sans-serif,system-ui,sans-serif}
.cl9-fig .ln{stroke:var(--ln);stroke-width:1.5;fill:none}
</style>
<!-- defs -->
<defs>
<marker id="cl9arrowfr" 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>
</defs>
<!-- title -->
<text x="400" y="16" text-anchor="middle" class="ti">Trois façons de stocker une closure dans une struct</text>
<!-- box1 -->
<rect x="300" y="26" width="200" height="50" rx="6" class="box"/>
<text x="400" y="47" text-anchor="middle" class="tx">struct Processor { op: ? }</text>
<text x="400" y="63" text-anchor="middle" class="mut">comment typer `op` ?</text>
<!-- fork -->
<path d="M400,76 L400,96 L150,96 L150,116" class="ln" marker-end="url(#cl9arrowfr)"/>
<path d="M400,96 L400,116" class="ln" marker-end="url(#cl9arrowfr)"/>
<path d="M400,96 L650,96 L650,116" class="ln" marker-end="url(#cl9arrowfr)"/>
<!-- generic -->
<rect x="40" y="116" width="220" height="94" rx="6" class="boxac"/>
<text x="150" y="140" text-anchor="middle" class="tx">Générique &lt;F: Fn&gt;</text>
<text x="150" y="158" text-anchor="middle" class="mut">dispatch statique</text>
<text x="150" y="174" text-anchor="middle" class="mut">type fixe, zero-cost</text>
<!-- boxed -->
<rect x="290" y="116" width="220" height="94" rx="6" class="box"/>
<text x="400" y="140" text-anchor="middle" class="tx">Box&lt;dyn Fn&gt;</text>
<text x="400" y="158" text-anchor="middle" class="mut">dispatch dynamique</text>
<text x="400" y="174" text-anchor="middle" class="mut">heap, hétérogène</text>
<!-- lifetime -->
<rect x="540" y="116" width="220" height="94" rx="6" class="box"/>
<text x="650" y="140" text-anchor="middle" class="tx">Box&lt;dyn Fn + 'a&gt;</text>
<text x="650" y="158" text-anchor="middle" class="mut">lié à une lifetime</text>
<text x="650" y="174" text-anchor="middle" class="mut">capture des références</text>
</svg>
</div>

## 1. Struct générique (Static Dispatch)

Utilise un paramètre de type générique avec des bounds `Fn`/`FnMut`/`FnOnce`. Idéal pour des types de closures fixes.

### Exemple : Trait Fn

```rust
struct Processor<F>
where
    F: Fn(i32) -> i32, // Trait bound pour le type de closure
{
    operation: F,
    value: i32,
}

impl<F> Processor<F>
where
    F: Fn(i32) -> i32,
{
    fn run(&self) -> i32 {
        (self.operation)(self.value)
    }
}

fn main() {
    let adder = Processor {
        operation: |x| x + 5, // Closure capturée par valeur
        value: 10,
    };
    println!("{}", adder.run()); // 15
}
```

### Bornes et lifetimes, en résumé
- **Overhead runtime zéro** : Monomorphized pour chaque type de closure.
- **Type de closure fixe** : Ne peut pas stocker différentes closures dans la même struct.

## 2. Trait Object (Dynamic Dispatch)

Utilise `Box<dyn Fn...>` pour stocker des closures hétérogènes. Nécessite une allocation heap.

### Exemple : Box&lt;dyn Fn&gt;

```rust
struct DynamicProcessor<'a> {
    operation: Box<dyn Fn(i32) -> i32 + 'a>, // Trait object avec lifetime optionnel
    value: i32,
}

impl<'a> DynamicProcessor<'a> {
    fn run(&self) -> i32 {
        (self.operation)(self.value)
    }
}

fn main() {
    let multiplier = 2;
    let processor = DynamicProcessor {
        operation: Box::new(|x| x * multiplier), // Capture `multiplier`
        value: 10,
    };
    println!("{}", processor.run()); // 20
}
```

### Points clés
- **Annotation de lifetime** : Requise si la closure capture des références (ex : `Box<dyn Fn() -> &str + 'a>`).
- **Flexibilité** : Peut stocker n'importe quelle closure correspondant au trait.
- **Overhead** : Vtable lookup ([dynamic dispatch](https://mayorana.ch/fr/blog/dispatch-performance-rust)).

## 3. Exemples avancés de Storage

### Closures stateful avec FnMut
```rust
struct Counter<F>
where
    F: FnMut() -> i32,
{
    increment: F,
    current: i32,
}

impl<F> Counter<F>
where
    F: FnMut() -> i32,
{
    fn new(increment: F) -> Self {
        Self {
            increment,
            current: 0,
        }
    }
    
    fn next(&mut self) -> i32 {
        self.current += (self.increment)();
        self.current
    }
}

fn counter_example() {
    let mut step = 1;
    let mut counter = Counter::new(move || {
        let current_step = step;
        step += 1; // Mute l'état capturé
        current_step
    });
    
    println!("Count 1: {}", counter.next()); // 1
    println!("Count 2: {}", counter.next()); // 3 (1+2)
    println!("Count 3: {}", counter.next()); // 6 (3+3)
}
```

### Plusieurs closures dans une struct
```rust
struct EventHandler<F1, F2, F3>
where
    F1: Fn(&str),           // OnStart
    F2: FnMut(&str) -> bool, // OnProcess (returns continue flag)
    F3: FnOnce(),           // OnComplete
{
    on_start: F1,
    on_process: F2,
    on_complete: Option<F3>, // Option car FnOnce ne peut être appelée qu'une fois
}

impl<F1, F2, F3> EventHandler<F1, F2, F3>
where
    F1: Fn(&str),
    F2: FnMut(&str) -> bool,
    F3: FnOnce(),
{
    fn new(on_start: F1, on_process: F2, on_complete: F3) -> Self {
        Self {
            on_start,
            on_process,
            on_complete: Some(on_complete),
        }
    }
    
    fn handle_event(&mut self, event: &str) {
        (self.on_start)(event);
        
        let should_continue = (self.on_process)(event);
        
        if !should_continue {
            if let Some(complete_handler) = self.on_complete.take() {
                complete_handler(); // Consomme la closure FnOnce
            }
        }
    }
}

fn multiple_closures_example() {
    let mut processed_count = 0;
    
    let mut handler = EventHandler::new(
        |event| println!("🚀 Starting: {}", event),
        |event| {
            processed_count += 1;
            println!("⚙️  Processing: {} (count: {})", event, processed_count);
            processed_count < 3 // Continue jusqu'à 3 events
        },
        || println!("✅ Completed processing"),
    );
    
    handler.handle_event("event1");
    handler.handle_event("event2");
    handler.handle_event("event3"); // Déclenche completion
}
```

### Collections de Closures Hétérogènes

```rust
struct Pipeline {
    steps: Vec<Box<dyn FnMut(i32) -> i32>>,
}

impl Pipeline {
    fn new() -> Self {
        Self { steps: Vec::new() }
    }
    
    fn add_step<F>(mut self, step: F) -> Self
    where
        F: FnMut(i32) -> i32 + 'static,
    {
        self.steps.push(Box::new(step));
        self
    }
    
    fn execute(&mut self, input: i32) -> i32 {
        self.steps.iter_mut().fold(input, |acc, step| step(acc))
    }
}

fn pipeline_example() {
    let mut counter = 0;
    
    let mut pipeline = Pipeline::new()
        .add_step(|x| x * 2)           // Multiply by 2
        .add_step(|x| x + 10)          // Add 10
        .add_step(move |x| {           // Stateful step
            counter += 1;
            println!("Step {} processing: {}", counter, x);
            x
        })
        .add_step(|x| if x > 50 { x - 20 } else { x }); // Conditional
    
    println!("Result 1: {}", pipeline.execute(5));  // ((5*2)+10) = 20
    println!("Result 2: {}", pipeline.execute(15)); // ((15*2)+10) = 40
    println!("Result 3: {}", pipeline.execute(25)); // ((25*2)+10-20) = 40
}
```

## 4. Capturer des références (Lifetimes)

Si la closure capture des références, la struct doit déclarer des lifetimes pour assurer la validité :

```rust
struct RefProcessor<'a, F>
where
    F: Fn(&'a str) -> &'a str, // Lifetime lié à input/output
{
    process: F,
    data: &'a str,
}

impl<'a, F> RefProcessor<'a, F>
where
    F: Fn(&'a str) -> &'a str,
{
    fn new(process: F, data: &'a str) -> Self {
        Self { process, data }
    }
    
    fn run(&self) -> &'a str {
        (self.process)(self.data)
    }
}

fn reference_example() {
    let data = "hello world";
    let processor = RefProcessor::new(
        |s| &s[6..], // Retourne "world"
        data
    );
    
    println!("Processed: {}", processor.run()); // "world"
    
    // `data` doit survivre au `processor`
}
```

### Exemple plus complexe avec Multiple Lifetimes

```rust
struct DataTransformer<'a, 'b, F>
where
    F: Fn(&'a str, &'b str) -> String,
{
    transform: F,
    source: &'a str,
    template: &'b str,
}

impl<'a, 'b, F> DataTransformer<'a, 'b, F>
where
    F: Fn(&'a str, &'b str) -> String,
{
    fn new(transform: F, source: &'a str, template: &'b str) -> Self {
        Self {
            transform,
            source,
            template,
        }
    }
    
    fn execute(&self) -> String {
        (self.transform)(self.source, self.template)
    }
}

fn multiple_lifetimes_example() {
    let source_data = "John Doe";
    let template_string = "Hello, {}!";
    
    let transformer = DataTransformer::new(
        |source, template| {
            template.replace("{}", source)
        },
        source_data,
        template_string,
    );
    
    println!("{}", transformer.execute()); // "Hello, John Doe!"
}
```

## 5. Patterns avancés de Storage

### Builder Pattern avec Closures

```rust
struct ServiceBuilder<F1, F2>
where
    F1: Fn(&str) -> bool,  // Validator
    F2: Fn(&str) -> String, // Transformer
{
    validator: Option<F1>,
    transformer: Option<F2>,
    name: String,
}

impl<F1, F2> ServiceBuilder<F1, F2>
where
    F1: Fn(&str) -> bool,
    F2: Fn(&str) -> String,
{
    fn new(name: String) -> ServiceBuilder<fn(&str) -> bool, fn(&str) -> String> {
        ServiceBuilder {
            validator: None,
            transformer: None,
            name,
        }
    }
    
    fn with_validator<V>(self, validator: V) -> ServiceBuilder<V, F2>
    where
        V: Fn(&str) -> bool,
    {
        ServiceBuilder {
            validator: Some(validator),
            transformer: self.transformer,
            name: self.name,
        }
    }
    
    fn with_transformer<T>(self, transformer: T) -> ServiceBuilder<F1, T>
    where
        T: Fn(&str) -> String,
    {
        ServiceBuilder {
            validator: self.validator,
            transformer: Some(transformer),
            name: self.name,
        }
    }
}

fn builder_pattern_example() {
    let service = ServiceBuilder::new("EmailService".to_string())
        .with_validator(|email| email.contains('@') && email.contains('.'))
        .with_transformer(|email| email.to_lowercase());
    
    println!("Service: {}", service.name);
    
    if let Some(ref validator) = service.validator {
        println!("test@example.com is valid: {}", validator("test@example.com"));
    }
    
    if let Some(ref transformer) = service.transformer {
        println!("Transformed: {}", transformer("TEST@EXAMPLE.COM"));
    }
}
```

### State Machine avec Closures

```rust
use std::collections::HashMap;

struct StateMachine<S, E, F>
where
    S: Clone + Eq + std::hash::Hash + std::fmt::Debug,
    E: Clone + std::fmt::Debug,
    F: FnMut(&S, &E) -> S,
{
    current_state: S,
    transition: F,
    history: Vec<(S, E, S)>, // (from, event, to)
}

impl<S, E, F> StateMachine<S, E, F>
where
    S: Clone + Eq + std::hash::Hash + std::fmt::Debug,
    E: Clone + std::fmt::Debug,
    F: FnMut(&S, &E) -> S,
{
    fn new(initial_state: S, transition: F) -> Self {
        Self {
            current_state: initial_state,
            transition,
            history: Vec::new(),
        }
    }
    
    fn handle_event(&mut self, event: E) -> &S {
        let old_state = self.current_state.clone();
        let new_state = (self.transition)(&self.current_state, &event);
        
        println!("Transition: {:?} --{:?}--> {:?}", old_state, event, new_state);
        
        self.history.push((old_state, event, new_state.clone()));
        self.current_state = new_state;
        
        &self.current_state
    }
    
    fn get_state(&self) -> &S {
        &self.current_state
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ConnectionState {
    Disconnected,
    Connecting,
    Connected,
    Error,
}

#[derive(Debug, Clone)]
enum ConnectionEvent {
    Connect,
    Connected,
    Disconnect,
    Timeout,
    Error,
}

fn state_machine_example() {
    let mut connection = StateMachine::new(
        ConnectionState::Disconnected,
        |state, event| {
            use ConnectionState::*;
            use ConnectionEvent::*;
            
            match (state, event) {
                (Disconnected, Connect) => Connecting,
                (Connecting, Connected) => Connected,
                (Connecting, Timeout) => Error,
                (Connected, Disconnect) => Disconnected,
                (Error, Connect) => Connecting,
                _ => state.clone(), // No transition
            }
        },
    );
    
    // Test sequence
    connection.handle_event(ConnectionEvent::Connect);
    connection.handle_event(ConnectionEvent::Connected);
    connection.handle_event(ConnectionEvent::Disconnect);
    connection.handle_event(ConnectionEvent::Connect);
    connection.handle_event(ConnectionEvent::Timeout);
    
    println!("Final state: {:?}", connection.get_state());
    println!("History: {} transitions", connection.history.len());
}
```

### Cache avec Closures

```rust
use std::collections::HashMap;
use std::hash::Hash;

struct Cache<K, V, F>
where
    K: Clone + Eq + Hash,
    V: Clone,
    F: FnMut(&K) -> V,
{
    data: HashMap<K, V>,
    loader: F,
    stats: CacheStats,
}

#[derive(Debug, Default)]
struct CacheStats {
    hits: usize,
    misses: usize,
}

impl<K, V, F> Cache<K, V, F>
where
    K: Clone + Eq + Hash,
    V: Clone,
    F: FnMut(&K) -> V,
{
    fn new(loader: F) -> Self {
        Self {
            data: HashMap::new(),
            loader,
            stats: CacheStats::default(),
        }
    }
    
    fn get(&mut self, key: &K) -> V {
        if let Some(value) = self.data.get(key) {
            self.stats.hits += 1;
            value.clone()
        } else {
            self.stats.misses += 1;
            let value = (self.loader)(key);
            self.data.insert(key.clone(), value.clone());
            value
        }
    }
    
    fn stats(&self) -> &CacheStats {
        &self.stats
    }
    
    fn clear(&mut self) {
        self.data.clear();
        self.stats = CacheStats::default();
    }
}

fn cache_example() {
    // Cache avec closure qui "calcule" des valeurs coûteuses
    let mut cache = Cache::new(|key: &String| {
        println!("Computing expensive operation for: {}", key);
        std::thread::sleep(std::time::Duration::from_millis(100)); // Simulate work
        format!("result_for_{}", key)
    });
    
    // Test du cache
    let keys = vec!["a".to_string(), "b".to_string(), "a".to_string(), "c".to_string(), "b".to_string()];
    
    for key in keys {
        let start = std::time::Instant::now();
        let result = cache.get(&key);
        let duration = start.elapsed();
        println!("Key: {}, Result: {}, Time: {:?}", key, result, duration);
    }
    
    println!("Cache stats: {:?}", cache.stats());
    // Expected: 3 misses (a, b, c), 2 hits (a, b)
}
```

## Comment le Bound Remodèle l'API de la Struct

Choisir `Fn`, `FnMut` ou `FnOnce` pour le champ stocké n'est pas une décision locale : cela se propage vers l'extérieur, dans le receiver que doit prendre chaque méthode appelant la closure, puis dans la façon dont les appelants peuvent utiliser la struct :

<div class="svg-container" style="margin:2rem 0;">
<svg class="cl9b-fig" viewBox="0 0 800 310" width="100%" style="height:auto;max-width:780px;display:block;margin:0 auto;" role="img" aria-label="Correspondance entre le bound de la closure stockée, le receiver que doit prendre run et la limite qui en résulte pour les appelants de la struct">
<!-- style -->
<style>
.cl9b-fig{--bg:#f8fafc;--box:#ffffff;--tx:#1e293b;--mut:#64748b;--ln:#cbd5e1;--ac:#FF6B00}
:root.dark .cl9b-fig,[data-theme="dark"] .cl9b-fig{--bg:#0f172a;--box:#1e293b;--tx:#f8fafc;--mut:#94a3b8;--ln:#475569}
.cl9b-fig .box{fill:var(--box);stroke:var(--ln);stroke-width:1.5}
.cl9b-fig .boxac{fill:var(--box);stroke:var(--ac);stroke-width:2}
.cl9b-fig .ti{fill:var(--tx);font:700 14px ui-sans-serif,system-ui,sans-serif}
.cl9b-fig .hd{fill:var(--mut);font:700 11px ui-sans-serif,system-ui,sans-serif}
.cl9b-fig .tx{fill:var(--tx);font:600 12px ui-sans-serif,system-ui,sans-serif}
.cl9b-fig .mut{fill:var(--mut);font:11px ui-sans-serif,system-ui,sans-serif}
.cl9b-fig .ln{stroke:var(--ln);stroke-width:1.5;fill:none}
</style>
<!-- defs -->
<defs>
<marker id="cl9b-arrow2fr" 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="cl9b-arrow2acfr" 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>
<!-- title -->
<text x="400" y="20" text-anchor="middle" class="ti">Le bound stocké dicte le receiver, et le receiver dicte l'appelant</text>
<!-- headers -->
<text x="133" y="48" text-anchor="middle" class="hd">BOUND DU CHAMP</text>
<text x="400" y="48" text-anchor="middle" class="hd">run() DOIT PRENDRE</text>
<text x="667" y="48" text-anchor="middle" class="hd">CE QU'OBTIENT L'APPELANT</text>
<!-- row 1 -->
<rect x="20" y="64" width="225" height="54" rx="6" class="box"/>
<text x="133" y="88" text-anchor="middle" class="tx">F: Fn(i32) -&gt; i32</text>
<text x="133" y="106" text-anchor="middle" class="mut">lit seulement ses captures</text>
<path d="M245,91 L290,91" class="ln" marker-end="url(#cl9b-arrow2fr)"/>
<rect x="290" y="64" width="220" height="54" rx="6" class="box"/>
<text x="400" y="88" text-anchor="middle" class="tx">&amp;self</text>
<text x="400" y="106" text-anchor="middle" class="mut">emprunt partagé</text>
<path d="M510,91 L555,91" class="ln" marker-end="url(#cl9b-arrow2fr)"/>
<rect x="555" y="64" width="225" height="54" rx="6" class="box"/>
<text x="667" y="88" text-anchor="middle" class="tx">appels illimités</text>
<text x="667" y="106" text-anchor="middle" class="mut">plusieurs lecteurs à la fois</text>
<!-- row 2 -->
<rect x="20" y="130" width="225" height="54" rx="6" class="boxac"/>
<text x="133" y="154" text-anchor="middle" class="tx">F: FnMut(i32) -&gt; i32</text>
<text x="133" y="172" text-anchor="middle" class="mut">mute ses captures</text>
<path d="M245,157 L290,157" class="ln" marker-end="url(#cl9b-arrow2acfr)"/>
<rect x="290" y="130" width="220" height="54" rx="6" class="boxac"/>
<text x="400" y="154" text-anchor="middle" class="tx">&amp;mut self</text>
<text x="400" y="172" text-anchor="middle" class="mut">emprunt exclusif</text>
<path d="M510,157 L555,157" class="ln" marker-end="url(#cl9b-arrow2acfr)"/>
<rect x="555" y="130" width="225" height="54" rx="6" class="boxac"/>
<text x="667" y="154" text-anchor="middle" class="tx">appels illimités</text>
<text x="667" y="172" text-anchor="middle" class="mut">mais le binding doit être `mut`</text>
<!-- row 3 -->
<rect x="20" y="196" width="225" height="54" rx="6" class="box"/>
<text x="133" y="220" text-anchor="middle" class="tx">F: FnOnce(i32) -&gt; i32</text>
<text x="133" y="238" text-anchor="middle" class="mut">consomme ses captures</text>
<path d="M245,223 L290,223" class="ln" marker-end="url(#cl9b-arrow2fr)"/>
<rect x="290" y="196" width="220" height="54" rx="6" class="box"/>
<text x="400" y="220" text-anchor="middle" class="tx">self</text>
<text x="400" y="238" text-anchor="middle" class="mut">prend l'ownership</text>
<path d="M510,223 L555,223" class="ln" marker-end="url(#cl9b-arrow2fr)"/>
<rect x="555" y="196" width="225" height="54" rx="6" class="box"/>
<text x="667" y="220" text-anchor="middle" class="tx">un seul appel</text>
<text x="667" y="238" text-anchor="middle" class="mut">la struct disparaît ensuite</text>
<!-- caption -->
<text x="400" y="280" text-anchor="middle" class="mut">Le boxing ne change rien : `Box&lt;dyn FnMut&gt;` impose toujours `&amp;mut self` à la méthode.</text>
<text x="400" y="297" text-anchor="middle" class="mut">Note aussi les parenthèses de `(self.operation)(x)` — sans elles Rust cherche une méthode.</text>
</svg>
</div>

## Compromis en un coup d'œil
| Approche | Cas d'Usage | Trade-Offs |
|----------|-------------|------------|
| Générique (impl Fn) | Haute performance, type de closure fixe | Moins flexible, binary bloat |
| Trait Object | Comportement dynamique, closures multiples | Overhead runtime, heap allocation |
| Lifetime Annotated | Closures capturant des références | Assure la sécurité, ajoute complexité |

### Matrice de décision

```rust
// Guide de décision pour stocker des closures
fn decision_guide() {
    // ✅ Utilise Generic quand:
    // - Performance critique
    // - Type de closure connu au moment de la compilation
    // - Pas besoin de changer la closure après construction
    
    struct FastProcessor<F: Fn(i32) -> i32> {
        op: F,
    }
    
    // ✅ Utilise Trait Object quand:
    // - Besoin de flexibilité runtime
    // - Multiple types de closures possibles
    // - Configuration dynamique
    
    struct FlexibleProcessor {
        op: Box<dyn Fn(i32) -> i32>,
    }
    
    // ✅ Utilise Lifetimes quand:
    // - Closure capture des références
    // - Données empruntées depuis l'extérieur
    // - Éviter les allocations inutiles
    
    struct BorrowingProcessor<'a, F: Fn(&'a str) -> &'a str> {
        op: F,
        data: &'a str,
    }
}
```

## Erreurs courantes et solutions
### 1. Oublier le Trait Bound

```rust
// ❌ Erreur: trait bound manquant
/*
struct BadProcessor<F> {
    operation: F, // ERREUR: F n'a pas de bounds
}
*/

// ✅ Solution: ajouter trait bound
struct GoodProcessor<F>
where
    F: Fn(i32) -> i32,
{
    operation: F,
}
```

### 2. Lifetime Mismatch

```rust
// ❌ Problématique: lifetime insuffisant
fn lifetime_issues() {
    /*
    fn create_processor() -> RefProcessor<'???, ???> {
        let data = "local data";
        RefProcessor::new(|s| s, data) // ERREUR: data doesn't live long enough
    }
    */
    
    // ✅ Solution: utiliser owned data ou static lifetime
    fn create_processor() -> impl Fn(&str) -> String {
        |s| s.to_string() // Convert to owned
    }
}
```

### 3. Mutable Borrow Issues

```rust
// Gérer les closures FnMut correctement
struct MutableProcessor<F>
where
    F: FnMut() -> i32,
{
    operation: F,
}

impl<F> MutableProcessor<F>
where
    F: FnMut() -> i32,
{
    fn new(operation: F) -> Self {
        Self { operation }
    }
    
    fn run(&mut self) -> i32 { // &mut self requis pour FnMut
        (self.operation)()
    }
}

fn mutable_example() {
    let mut counter = 0;
    let mut processor = MutableProcessor::new(|| {
        counter += 1;
        counter
    });
    
    println!("Run 1: {}", processor.run()); // 1
    println!("Run 2: {}", processor.run()); // 2
}
```

## Points clés

**Structs génériques : Meilleures pour performance et static dispatch.**  
**[Trait objects](https://mayorana.ch/fr/blog/object-safety-rust) : Utilise quand tu stockes des closures hétérogènes.**  
**Lifetimes : Requis si la closure capture des références.**

### Règles pratiques

1. **Performance first** → Generic avec trait bounds
2. **Flexibilité needed** → Box<dyn Trait>
3. **References captured** → Explicit lifetimes
4. **Mutable state** → FnMut avec &mut self
5. **One-time use** → FnOnce avec Option<F>

Stocke dans une struct une closure qui a capturé `&mut` et la mutabilité remonte : le binding de
la struct doit être `mut` lui aussi, et la borne du champ doit être `FnMut`.

## Un cas complet
```rust,ignore
use std::collections::HashMap;

// Système de plugins avec closures stockées
struct PluginSystem<'a> {
    plugins: HashMap<String, Box<dyn FnMut(&str) -> String + 'a>>,
    middleware: Vec<Box<dyn Fn(&str, &str) -> String + 'a>>,
}

impl<'a> PluginSystem<'a> {
    fn new() -> Self {
        Self {
            plugins: HashMap::new(),
            middleware: Vec::new(),
        }
    }
    
    fn register_plugin<F>(&mut self, name: &str, plugin: F)
    where
        F: FnMut(&str) -> String + 'a,
    {
        self.plugins.insert(name.to_string(), Box::new(plugin));
    }
    
    fn add_middleware<F>(&mut self, middleware: F)
    where
        F: Fn(&str, &str) -> String + 'a,
    {
        self.middleware.push(Box::new(middleware));
    }
    
    fn execute(&mut self, plugin_name: &str, input: &str) -> Option<String> {
        if let Some(plugin) = self.plugins.get_mut(plugin_name) {
            let mut result = plugin(input);
            
            // Apply middleware
            for middleware in &self.middleware {
                result = middleware(plugin_name, &result);
            }
            
            Some(result)
        } else {
            None
        }
    }
}

fn plugin_system_example() {
    let mut system = PluginSystem::new();
    
    // Register plugins avec différentes closures
    let mut counter = 0;
    system.register_plugin("counter", move |_input| {
        counter += 1;
        format!("Count: {}", counter)
    });
    
    system.register_plugin("uppercase", |input| {
        input.to_uppercase()
    });
    
    system.register_plugin("reverse", |input| {
        input.chars().rev().collect()
    });
    
    // Add middleware
    system.add_middleware(|plugin_name, result| {
        format!("[{}] {}", plugin_name, result)
    });
    
    system.add_middleware(|_plugin_name, result| {
        format!("🔧 {}", result)
    });
    
    // Test le système
    println!("{:?}", system.execute("counter", "test"));     // Some("🔧 [counter] Count: 1")
    println!("{:?}", system.execute("uppercase", "hello"));  // Some("🔧 [uppercase] HELLO")
    println!("{:?}", system.execute("reverse", "world"));    // Some("🔧 [reverse] dlrow")
    println!("{:?}", system.execute("counter", "test"));     // Some("🔧 [counter] Count: 2")
    println!("{:?}", system.execute("missing", "test"));     // None
}

fn main() {
    counter_example();
    println!("---");
    multiple_closures_example();
    println!("---");
    pipeline_example();
    println!("---");
    state_machine_example();
    println!("---");
    cache_example();
    println!("---");
    plugin_system_example();
}
```

---

**Conclusion :** Stocker des closures dans des structs offre une flexibilité énorme pour créer des architectures modulaires et expressives. Choisis l'approche selon tes besoins de performance, flexibilité et sécurité des lifetimes !
