IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Rust Discussion :

Emprunt et fonctions de rappel


Sujet :

Rust

  1. #1
    Membre émérite
    Avatar de Nothus
    Homme Profil pro
    aucun
    Inscrit en
    Juillet 2009
    Messages
    200
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : aucun
    Secteur : Conseil

    Informations forums :
    Inscription : Juillet 2009
    Messages : 200
    Points : 2 575
    Points
    2 575
    Billets dans le blog
    27
    Par défaut Emprunt et fonctions de rappel
    Bonjour,

    Je m'entraîne depuis plusieurs semaines en Rust. Je bloque (comme tous les nouveaux j'imagine), sur la question de l'emprunt.

    Voici un code basique que j'ai écrit. Il reprend d'événements qui font un appel à des fonctions de rappel ("sauce Javascript"). En soi le code compile et fonctionne...

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
     
     
    use std::collections::HashMap; 
     
    #[allow(dead_code)] 
    #[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)] 
    enum EvenementType{ 
    	Load, 
    	Unload 
    } 
    // enum EvenementValeur{}
     
    #[derive(Debug)]
    struct Evenement<T> { 
    	evt_type: EvenementType, 
    	evt_valeur: T 
    } 
     
    struct Objet<T> { 
    	valeur: T, 
    	evt_listeners: HashMap<EvenementType,Vec<fn(&mut Objet<T>, &Evenement<T>)>> 
    } 
     
    impl<T> Objet<T> { 
     
    	fn add_event_listener( &mut self, evt_type: EvenementType, evt_rappel: fn(&mut Objet<T>, &Evenement<T>) ) { 
    		if !self.evt_listeners.contains_key( &evt_type ) { 
    			self.evt_listeners.insert( 
    				evt_type.clone(), 
    				vec!() 
    			); 
    		} 
    		if let Some( vecteur ) = self.evt_listeners.get_mut( &evt_type ) { 
    			vecteur.push( 
    				evt_rappel 
    			); 
    		} 
    	} 
     
    	fn raise_event( &mut self, evt: &Evenement<T> ) { // <----------- fonction bien pensée ?? 
    		let mut vecteur: Option<Vec<fn(&mut Objet<T>, &Evenement<T>)>> = None; 
    		if let Option::Some( v ) = &self.evt_listeners.get( &evt.evt_type ) { 
    			vecteur = Option::Some( v.to_vec() ); // <------------- revient à cloner le vecteur... 
    		} 
    		if let Some( v ) = vecteur { 
    			for rappel in v { 
    				rappel( self, evt ); 
    			} 
    		} 
    	} 
    } 
     
    fn main() { 
     
    	let mut o = Objet::<usize> { 
    		valeur: 0, 
    		evt_listeners: HashMap::new() 
    	}; 
     
    	println!("v initiale : {:?}", o.valeur); 
     
    	o.add_event_listener( 
    		EvenementType::Load, 
    		|obj:&mut Objet<usize>, evt: &Evenement<usize>| { 
    			obj.valeur = evt.evt_valeur; 
    		} 
    	); 
     
    	let e = Evenement { 
    		evt_type: EvenementType::Load, 
    		evt_valeur: 1 
    	}; 
     
    	o.raise_event( &e ); 
     
    	println!("v finale : {:?}", o.valeur); 
     
    }

    Ma fonction "raise_event" me pose soucis. Elle prend en argument un événement, partagé de manière immuable (non-mutable) ainsi que l'objet visé via une référence altérable (mutable). La fonction va chercher dans un HashMap si le type d'événement est connu et si oui, fait appel à des fonctions stockées comme valeurs d'un vecteur, lui-même stocké comme valeur du HashMap.

    Problème : l'accès au HashMap impose un emprunt de 'self' ce qui me bloque ensuite un nouvel emprunt altérable de 'self' pour la fonction de rappel qui est appelée (ce qui ne compile évidemment pas).

    La solution la plus "élégante" que j'ai trouvé, est de faire un clonage du vecteur des fonctions de rappel. J'ai bien pensé à implémenter le trait Copy, mais ça me semble impossible dans cette situation (j'ai tort ?).

    Du coup j'ai en mémoire deux fois le vecteur (l'un dans le HashMap, l'autre pour l'utilisation immédiate dans le bloc suivant). Certes en soi ce doublement n'est pas long, le clone étant rapidement retirée de la mémoire, mais l'idéal serait de ne pas faire ce fichu clonage... et donc de contourner élégamment la limitation d'emprunt.

    Qu'en pensez-vous ? Là je sèche...

    Merci à tous,

    Julien.
    "En dehors des langages de la famille LISP et du modèle RDF, point de salut."

  2. #2
    Membre émérite
    Avatar de Nothus
    Homme Profil pro
    aucun
    Inscrit en
    Juillet 2009
    Messages
    200
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : aucun
    Secteur : Conseil

    Informations forums :
    Inscription : Juillet 2009
    Messages : 200
    Points : 2 575
    Points
    2 575
    Billets dans le blog
    27
    Par défaut
    Bonjour à tous,

    Finalement un déjeuner et ça repart : je me réponds à moi-même... après une relecture de la doc :
    https://doc.rust-lang.org/nomicon/borrow-splitting.html (le premier exemple donné)

    J'ai donc simplifié le problème, en partageant de manière altérable seulement un champ précis de la structure (ce que le compilateur autorise, même dans un cas où un emprunt non-mutable existe précedemment semble-t-il). Comme ce champ est générique, il supporte donc tous les types.

    Ainsi je peux avoir une structure "conteneur" qui gère les événements et eux-seuls, et donne à la fonction de rappel, seulement le champ mutable qu'elle a besoin, c'est-à-dire l'information "utile".

    Problème résolu ! Si par contre vous avez de meilleures idées, je suis preneur :]

    Le code modifié :

    Code rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
     
     
    use std::collections::HashMap; 
     
    #[allow(dead_code)] 
    #[derive(Debug,Copy,Clone,PartialEq,Eq,Hash)] 
    enum EvenementType{ 
    	Load, 
    	Unload 
    } 
    // enum EvenementValeur{}
     
    #[derive(Debug)]
    struct Evenement<T> { 
    	evt_type: EvenementType, 
    	evt_valeur: T 
    } 
     
    struct Conteneur<T> { 
    	objet: T, 
    	ecouteurs: HashMap<EvenementType,Vec<fn(&mut T, &Evenement<T>)>> 
    } 
     
    impl<T> Conteneur<T> { 
     
    	fn add_event_listener( &mut self, evt_type: EvenementType, evt_rappel: fn(&mut T, &Evenement<T>) ) { 
    		if !self.ecouteurs.contains_key( &evt_type ) { 
    			self.ecouteurs.insert( 
    				evt_type.clone(), 
    				vec!() 
    			); 
    		} 
    		if let Some( vecteur ) = self.ecouteurs.get_mut( &evt_type ) { 
    			vecteur.push( 
    				evt_rappel 
    			); 
    		} 
    	} 
     
    	fn raise_event( &mut self, evt: &Evenement<T> ) { 
    		if let Option::Some( vecteur ) = &self.ecouteurs.get( &evt.evt_type ) { 
    			for rappel in &**vecteur { 
    				rappel( &mut self.objet, evt ); // <------ plus besoin de copier tout le vecteur 
    			}  
    		} 
    	} 
    } 
     
    #[derive(Debug)] 
    struct ObjTest { 
    	valeur: usize 
    } 
     
    fn main() { 
     
    	// --- --- --- 
    	println!("\n-_- partie 1 (via 'struct') -_-\n"); 
    	// --- --- --- 
     
    	let mut o = Conteneur::<ObjTest> { 
    		objet: ObjTest {
    			valeur: 0 
    		}, 
    		ecouteurs: HashMap::new() 
    	}; 
     
    	println!("v initiale : {:?}", o.objet); 
     
    	o.add_event_listener( 
    		EvenementType::Load, 
    		|obj: &mut ObjTest, evt: &Evenement<ObjTest>| { 
    			obj.valeur = evt.evt_valeur.valeur; 
    		} 
    	); 
     
    	let e = Evenement { 
    		evt_type: EvenementType::Load, 
    		evt_valeur: ObjTest {
    			valeur: 1 
    		} 
    	}; 
     
    	o.raise_event( &e ); 
     
    	println!("v finale : {:?}", o.objet); 
     
    	// --- --- --- 
    	println!("\n-_- partie 2 (via 'Box') -_-\n");
    	// --- --- --- 
     
    	let mut o = Conteneur::<Box<usize>> { 
    		objet: Box::new( 0 ), 
    		ecouteurs: HashMap::new() 
    	}; 
     
    	println!("v initiale : {:?}", o.objet); 
     
    	o.add_event_listener( 
    		EvenementType::Load, 
    		|obj: &mut Box<usize>, evt: &Evenement<Box<usize>>| { 
    			*obj = Box::new( *evt.evt_valeur ); 
    		} 
    	); 
     
    	let e = Evenement { 
    		evt_type: EvenementType::Load, 
    		evt_valeur: Box::new( 1 ), 
    	}; 
     
    	o.raise_event( &e ); 
     
    	println!("v finale : {:?}", o.objet); 
     
    	// --- --- --- 
    	println!("\n-_- partie 3 (via type primitif) -_-\n");
    	// --- --- --- 
     
    	let mut o = Conteneur::<usize> { 
    		objet: 0, 
    		ecouteurs: HashMap::new() 
    	}; 
     
    	println!("v initiale : {:?}", o.objet); 
     
    	o.add_event_listener( 
    		EvenementType::Load, 
    		|obj: &mut usize, evt: &Evenement<usize>| { 
    			*obj = evt.evt_valeur; 
    		} 
    	); 
     
    	let e = Evenement { 
    		evt_type: EvenementType::Load, 
    		evt_valeur: 1, 
    	}; 
     
    	o.raise_event( &e ); 
     
    	println!("v finale : {:?}", o.objet); 
     
    }

    Bonne journée,

    Julien.
    "En dehors des langages de la famille LISP et du modèle RDF, point de salut."

  3. #3
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Bonjour,

    Le code de add_event_listener peut être beaucoup plus court et légèrement plus performant.

    Je remplacerais :
    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    	fn add_event_listener( &mut self, evt_type: EvenementType, evt_rappel: fn(&mut T, &Evenement<T>) ) { 
    		if !self.ecouteurs.contains_key( &evt_type ) { 
    			self.ecouteurs.insert( 
    				evt_type.clone(), 
    				vec!() 
    			); 
    		} 
    		if let Some( vecteur ) = self.ecouteurs.get_mut( &evt_type ) { 
    			vecteur.push( 
    				evt_rappel 
    			); 
    		} 
    	}

    par :
    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    	fn add_event_listener(&mut self, evt_type: EvenementType, evt_rappel: fn(&mut T, &Evenement<T>)) {
    		self.ecouteurs.entry(evt_type).or_default().push(evt_rappel);
    	}
    Dans cette nouvelle version, il n'y a qu'une seule recherche faite dans la table de hachage, via la méthode entry.
    La méthode or_default retourne une référence muable sur la valeur existante ou, s'il n'y a pas de valeur, crée une valeur par défaut (en l'occurrence un vecteur vide) et retourne une référence muable dessus.

  4. #4
    Membre émérite
    Avatar de Nothus
    Homme Profil pro
    aucun
    Inscrit en
    Juillet 2009
    Messages
    200
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : aucun
    Secteur : Conseil

    Informations forums :
    Inscription : Juillet 2009
    Messages : 200
    Points : 2 575
    Points
    2 575
    Billets dans le blog
    27
    Par défaut
    Ah merci @Pyramidev. Effectivement : en plus d'être davantage efficace, c'est beaucoup plus lisible. Merci beaucoup !

    On ne le répétera jamais assez : la doc, la doc, la doc... il n'y a que ça dans la vie
    "En dehors des langages de la famille LISP et du modèle RDF, point de salut."

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. fonction de rappel et délégués
    Par acheo dans le forum C#
    Réponses: 1
    Dernier message: 13/04/2011, 09h11
  2. Contrôleur de personnage et fonctions de rappel
    Par supermael dans le forum PhysX et APEX
    Réponses: 1
    Dernier message: 08/10/2010, 22h28
  3. Réponses: 5
    Dernier message: 25/05/2010, 21h10
  4. Réponses: 2
    Dernier message: 27/04/2010, 17h33
  5. SugarCRM : Fonction de rappel par mail
    Par AnthOO42 dans le forum SugarCRM
    Réponses: 0
    Dernier message: 14/01/2010, 17h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo