2017-01-19 2 views
1

Passing Verschlüsse in Rust ist recht unkompliziert, aber bei der Speicherung von Verschlüssen für die Wiederverwendung gibt es mehrere Lösungen (verwenden Sie generische Funktionsarten, Referenzverschlüsse oder Box, Box mit 'static Lebensdauer oder nicht? ...).Idiomatische Art, einen Verschluss zur Wiederverwendung aufzubewahren?

Während ich durch diese mehrere Male mit verschiedenen Arten von Boxed-Typen verwirrt habe, lesen Sie über ähnliche Q & A's und könnte sogar eine Vermutung bei der Beantwortung dieser Frage riskieren. Ich habe kein Gefühl dafür, wie ich damit umgehen soll, selbst für den einfachen/offensichtlichen Fall, oder was für ein guter Ausgangspunkt wäre.

Um die Frage konkreter zu machen, was wäre ein guter Weg, um dieses Beispiel speichern Funktionen zur Wiederverwendung, mit dem Builder-Muster zu speichern Schließungen später aufgerufen werden.

// This example looks a bit long but its really very simple. 

// * This example is most of the way to implementing the builder pattern, 
// it ust runs the code immediately instead of storing input 
// to run on `build()`. 
// * Changes should only be needed where the comment `stored closures:` 
// has been written. 
// * I've attempted to make this example as generic as possible, 
// but not _so_ simple that the answer wont apply to real-world use (hopefully!). 

struct MyActions { 
    num: i32, 
    times: i32, 

    // stored closures: should be stored here. 
    // update_fn: Option<Fn(...)>, 
    // twiddle_fn: Option<Fn(...)>, 
} 

impl MyActions { 
    pub fn new(num: i32) -> Self { 
     return MyActions { 
      num: num, 
      times: 1, 
     } 
    } 

    pub fn build(self) -> i32 { 
     // stored closures: 
     // should run both actions if they're defined and return the result. 
     return self.num; 
    } 

    pub fn num_times(mut self, times: i32) -> Self { 
     self.times = times; 
     self 
    } 

    pub fn num_update<F>(mut self, func: F) -> Self 
     where 
     F: Fn(i32) -> i32 
    { 
     // stored closures: run immediately for now 
     for _ in 0..self.times { 
      self.num = func(self.num); 
     } 
     self 
    } 

    pub fn num_twiddle<F>(mut self, func: F) -> Self 
     where 
     F: Fn(i32) -> i32 
    { 
     // stored closures: run immediately for now 
     for _ in 0..self.times { 
      self.num = func(self.num); 
     } 
     self 
    } 
} 

// no changes needed here 
fn main() { 
    let act = MyActions::new(133); 
    let num_other: i32 = 4; 

    // builder pattern (currently executes immediately). 
    let result = act 
     .num_times(8) 
     .num_update(|x| x * 2 + num_other) 
     .num_twiddle(|x| (((x/2) - 1)^(x + 1))^num_other) 
     .build(); 

    // Lets say we would want this example to work, 
    // where 'times' is set after defining both functions. 
    /* 
    let result = act 
     .num_update(|x| x * 2 + num_other) 
     .num_twiddle(|x| (((x/2) - 1)^(x + 1))^num_other) 
     .num_times(8) // <-- order changed here 
     .build(); 
    */ 

    println!("done: {}", result); 
} 

Antwort

3

Die idiomatische Lösung besteht darin, die Verschlüsse zu verschließen. Während das Einschließen eines Closings und das spätere Aufrufen zusätzlichen Overhead und dynamischen Versand-Overhead verursachen, ist es in den meisten Fällen vernachlässigbar und der Typ MyAction bleibt mit freundlichen Fehlermeldungen leicht verwendbar.

Alternativ können die verschiedenen Funktionen generische Felder der Struktur MyAction sein, die den Abschluss ohne Indirection speichern. Dies kann in einigen Fällen zu enormen Beschleunigungen führen, aber die Verwendbarkeit eines solchen Typs verringert sich aufgrund komplexerer Fehlermeldungen und der Unfähigkeit, Objekte frei herumzubewegen.

Wenn die Box-Version im Profiling als langsam angezeigt wird, können Sie zur generischen Version wechseln. Ansonsten schlage ich vor, bei der einfach zu verwendenden Box-Version zu bleiben.

Box mit 'statische Lebensdauer oder nicht?

Auch hier der Einfachheit halber können Sie die 'static Lebensdauer verwenden, aber dann MyAction Struktur kann nur Schließungen, die ihre Umwelt nicht leihen. Wenn Sie eine Lebensdauer für die Struktur MyAction verwenden und diese an die Closures weiterleiten, können Sie Ihre Umgebung auf Kosten von generischen Argumenten ausleihen, was wiederum dazu führen könnte, dass die Struktur MyAction schwieriger zu verwenden ist.

2

Der Vollständigkeit halber, da einige der Syntax für die Schließung Besitz nicht offensichtlich ist, hier ist mein Versuch, eine idiomatische/rustikale Version des Codes in der Frage.

(Wenn es irgendwelche Probleme damit gibt, bitte kommentieren oder aktualisieren Sie es direkt).

  • Gebrauchte Boxed Schließung im Allgemeinen scheint dies die bevorzugte Methode zu sein, da es mehrere verschiedene Verschlusstypen bedeutet, können in der gleichen Struktur gespeichert werden.
  • Ohne Verwendung von 'static kann der Aufrufer Variablen in seiner Umgebung verwenden (siehe num_other Verwendung in der Frage). Verwenden Sie stattdessen eine Lebensdauer von struct.

Arbeitsbeispiel boxed Verschlüsse mit Vererbungs des Behälters structs Lebensdauer:

use std::boxed::Box; 

struct MyActions<'a> { 
    num: i32, 
    times: i32, 

    update_fn: Option<Box<Fn(i32) -> i32 + 'a>>, 
    twiddle_fn: Option<Box<Fn(i32) -> i32 + 'a>>, 
} 

impl <'a> MyActions<'a> { 
    pub fn new(num: i32) -> Self { 
     return MyActions { 
      num: num, 
      times: 1, 
      update_fn: None, 
      twiddle_fn: None, 
     } 
    } 

    pub fn build(self) -> i32 { 
     let mut num = self.num; 

     if let Some(update_fn) = self.update_fn { 
      for _ in 0..self.times { 
       num = update_fn(num); 
      } 
     } 

     if let Some(twiddle_fn) = self.twiddle_fn { 
      for _ in 0..self.times { 
       num = twiddle_fn(num); 
      } 
     } 

     return num; 
    } 

    pub fn num_times(mut self, times: i32) -> Self { 
     self.times = times; 
     self 
    } 

    pub fn num_update<F>(mut self, func: F) -> Self 
     where 
     F: 'a, 
     F: Fn(i32) -> i32, 
    { 
     self.update_fn = Some(Box::new(func)); 
     self 
    } 

    pub fn num_twiddle<F>(mut self, func: F) -> Self 
     where 
     F: 'a, 
     F: Fn(i32) -> i32, 
    { 
     self.twiddle_fn = Some(Box::new(func)); 
     self 
    } 
} 

// no changes needed here 
fn main() { 
    let act = MyActions::new(133); 
    let num_other: i32 = 4; 

    // builder pattern (currently executes immediately). 
    let result = act 
     .num_times(8) 
     .num_update(|x| x * 2 + num_other) 
     .num_twiddle(|x| (((x/2) - 1)^(x + 1))^num_other) 
     .build(); 

    println!("done: {}", result); 

    // Lets say we would want this example to work, 
    // where 'times' is set after defining both functions. 
    let act = MyActions::new(133); 
    let result = act 
     .num_update(|x| x * 2 + num_other) 
     .num_twiddle(|x| (((x/2) - 1)^(x + 1))^num_other) 
     .num_times(8) // <-- order changed here 
     .build(); 

    println!("done: {}", result); 
} 
Verwandte Themen