2016-03-26 1 views
2

J'ai une fonction coûteuse comme ceci:Quelle est la manière idiomatique d'implémenter la mise en cache sur une fonction qui n'est pas une méthode struct?

pub fn get_expensive_value(n: u64): u64 { 
    let ret = 0; 
    for 0 .. n { 
     // expensive stuff 
    } 
    ret 
} 

Et il est appelé très souvent avec le même argument. C'est pur, ce qui signifie qu'il retournera le même résultat et pourra utiliser un cache.

S'il s'agissait d'une méthode struct, j'ajouterais un membre à la structure qui agit comme un cache, mais ce n'est pas le cas. Donc, mon option semble être d'utiliser une statique:

static mut LAST_VAL: Option<(u64, u64)> = None; 

pub fn cached_expensive(n: u64) -> u64 { 
    unsafe { 
     LAST_VAL = LAST_VAL.and_then(|(k, v)| { 
      if k == n { 
       Some((n,v)) 
      } else { 
       None 
      } 
     }).or_else(|| { 
      Some((n, get_expensive_value(n))) 
     }); 
     let (_, v) = LAST_VAL.unwrap(); 
     v 
    } 
} 

Maintenant, j'ai dû utiliser unsafe. Au lieu de static mut, je pourrais mettre un RefCell dans un const. Mais je ne suis pas convaincu que ce soit plus sûr - cela évite juste d'avoir à utiliser le bloc unsafe. Je pensais à un Mutex, mais je ne pense pas que cela m'amène à la sécurité des threads non plus.

Redessiner le code pour utiliser une struct pour le stockage n'est pas vraiment une option.

+0

en double de [Comment puis-je créer un singleton global, mutable?] (Http://stackoverflow.com/questions/27791532/how- do-i-create-a-global-mutable-singleton). – Shepmaster

+0

Ou vous pouvez modifier la signature de 'cached_expensive' pour accepter le cache comme un autre paramètre. – Shepmaster

+0

Je ne pense pas que ce soit un doublon. Ma question concerne spécifiquement la mise en cache et, bien que mon point de départ soit un singleton global et mutable, c'est une bonne solution qui pourrait expliquer comment (par exemple) un "RefCell" ou "Mutex" pourrait améliorer cela, ou offrir une solution complète. alternative différente structurellement. –

Répondre

1

Je pense que la meilleure alternative est d'utiliser une variable globale avec un mutex. L'utilisation lazy_static facilite et permet la déclaration « globale » dans la fonction

pub fn cached_expensive(n: u64) -> u64 { 
    use std::sync::Mutex; 
    lazy_static! { 
     static ref LAST_VAL: Mutex<Option<(u64, u64)>> = Mutex::new(None); 
    } 
    let mut last = LAST_VAL.lock().unwrap(); 
    let r = last.and_then(|(k, v)| { 
     if k == n { 
      Some((n, v)) 
     } else { 
      None 
     } 
    }).or_else(|| Some((n, get_expensive_value(n)))); 
    let (_, v) = r.unwrap(); 
    *last = r; 
    v 
} 
+0

Donc vous êtes d'accord qu'il s'agit d'un doublon de [Comment créer un singleton mutable global?] (Http://stackoverflow.com/questions/27791532/how -do-i-create-a-global-mutable-singleton) comme suggéré dans les commentaires? – Shepmaster

+0

Oui. Mais je décide d'écrire la réponse pour montrer que la variable globale peut être déclarée dans la fonction, donc la portée est locale. – malbarbo