2016-09-10 1 views
1

Ce sont les moyens que je connais pour créer singletons à Rust:Comment faire un run de destructeur de Rust Singleton?

#[macro_use] 
extern crate lazy_static; 

use std::sync::{Mutex, Once, ONCE_INIT}; 

#[derive(Debug)] 
struct A(usize); 
impl Drop for A { 
    fn drop(&mut self) { 
     // This is never executed automatically. 
     println!(
      "Dropping {:?} - Important stuff such as release file-handles etc.", 
      *self 
     ); 
    } 
} 

// ------------------ METHOD 0 ------------------- 
static PLAIN_OBJ: A = A(0); 

// ------------------ METHOD 1 ------------------- 
lazy_static! { 
    static ref OBJ: Mutex<A> = Mutex::new(A(1)); 
} 

// ------------------ METHOD 2 ------------------- 
fn get() -> &'static Mutex<A> { 
    static mut OBJ: *const Mutex<A> = 0 as *const Mutex<A>; 
    static ONCE: Once = ONCE_INIT; 
    ONCE.call_once(|| unsafe { 
     OBJ = Box::into_raw(Box::new(Mutex::new(A(2)))); 
    }); 
    unsafe { &*OBJ } 
} 

fn main() { 
    println!("Obj = {:?}", PLAIN_OBJ); // A(0) 
    println!("Obj = {:?}", *OBJ.lock().unwrap()); // A(1) 
    println!("Obj = {:?}", *get().lock().unwrap()); // A(2) 
} 

Aucune de ces appels A de destructor (drop()) à la sortie du programme. Ce comportement est attendu pour la méthode 2 (qui est allouée par tas), mais je n'ai pas examiné l'implémentation de lazy_static! pour savoir si elle allait être similaire.

Il n'y a pas de RAII ici. Je pourrais réaliser ce comportement d'un singleton RAII en C++ (j'avais l'habitude de coder en C++ jusqu'à un an en arrière, donc la plupart de mes comparaisons le concernent - je ne connais pas beaucoup d'autres langages) en utilisant la fonction locale statique:

A& get() { 
    static A obj; // thread-safe creation with C++11 guarantees 
    return obj; 
} 

Ceci est probablement alloué/créé (paresseusement) dans la zone définie par l'implémentation et est valide pour la durée de vie du programme. Lorsque le programme se termine, le destructeur est exécuté de manière déterministe. Nous devons éviter d'y accéder à partir des destructeurs d'autres statiques, mais je n'ai jamais rencontré cela.

Je pourrais avoir besoin de libérer des ressources et je veux que drop() soit exécuté. En ce moment, je finis par le faire manuellement juste avant la fin du programme (vers la fin de la main après que tous les threads se soient joints, etc.).

Je ne sais même pas comment faire cela en utilisant lazy_static!, donc j'ai évité de l'utiliser et seulement aller pour la méthode 2 où je peux le détruire manuellement à la fin.

Je ne veux pas faire ça; Y at-il un moyen que je puisse avoir un tel singleton RAII se comporter dans Rust?

Répondre

6

Les singletons en particulier, et les constructeurs/destructeurs globaux en général, sont un fléau (en particulier dans un langage tel que C++). Je dirais que les principaux problèmes (fonctionnels) qu'ils causent sont connus respectivement sous ordre d'initialisation statique (ou de destruction) fiasco. C'est-à-dire, il est facile de créer accidentellement un cycle de dépendance entre ces globals, et même sans un tel cycle, il n'est pas immédiatement clair pour le compilateur dans quel ordre ils devraient être construits/détruits.

Ils peuvent également causer d'autres problèmes: démarrage plus lent, la mémoire partagée accidentellement, ...

à Rust, l'attitude adoptée a été Pas de vie avant/après principale. En tant que tel, tenter d'obtenir le comportement C++ ne fonctionnera probablement pas comme prévu.

Vous obtiendrez le soutien linguistique beaucoup plus si vous:

  • laissez tomber l'aspect global
  • laisser tomber la tentative d'avoir une seule instance

(et en prime, il va être aussi beaucoup plus facile à tester en parallèle, aussi)

Ma recommandation, donc, est de simplement coller avec des variables locales. Instanciez-le en main, passez-le par valeur/référence dans la pile d'appel, et évitez non seulement ces problèmes d'ordre d'initialisation difficiles, mais aussi la destruction.

+3

Nous avons besoin d'un bot pour répondre automatiquement à chaque question avec le mot "singleton" avec "vous ne voulez pas vraiment faire ça". – Shepmaster

+0

En plus des excellents points ici, j'ajouterais aussi le multithreading à la liste des cas problématiques. – Shepmaster

+1

Il a sa place - voici un exemple simple dans Rust lui-même: disons que vous avez besoin d'un seul objet pour persister dans tous vos tests (qui fonctionnent comme des threads séparés en parallèle lors du 'cargo test'). Peut-être s'agit-il de simuler un réseau, d'écrire dans un seul fichier après un tas de transformations en fonction de paramètres globaux qui changent à mesure que les tests s'exécutent et qu'il est important que les autres tests voient les changements. Le motif est trop abstrait pour être simplement saccagé et même si certains doivent être rarement utilisés, ils ont toujours leur place. Mais vous répondez à ma question que ce n'est pas possible - donc si je n'en reçois pas d'autres, je vais accepter ceci - merci – ustulation