2016-05-18 12 views
2

Ich habe eine Struktur, die wir mit dem Builder-Muster erstellen können, da es einige Felder Option al.Standard generischer Parameter

Wenn ich die Builder-Funktionen verwenden, um diese optionalen Felder anzugeben, muss ich die generischen Parameter nicht angeben.

Aber wenn ich diese Funktionen nicht aufrufen, muss ich die generischen Parameter angeben. Hier

ein Beispiel:

use Structs::*; 

struct Struct<T, F: Fn(T)> { 
    func: Option<F>, 
    value: T, 
} 

enum Structs<T, F: Fn(T)> { 
    Struct1(T), 
    Struct2(T, F), 
} 

impl<T, F: Fn(T)> Struct<T, F> { 
    fn new(value: T) -> Struct<T, F> { 
     Struct { 
      func: None, 
      value: value, 
     } 
    } 

    fn build(self) -> Structs<T, F> { 
     if let Some(func) = self.func { 
      Struct2(self.value, func) 
     } 
     else { 
      Struct1(self.value) 
     } 
    } 

    fn func(mut self, func: F) -> Struct<T, F> { 
     self.func = Some(func); 
     self 
    } 
} 

fn main() { 
    let _strct = Struct::new(42) 
     .func(|n| { println!("{}", n); }) 
     .build(); 

    //let _strct = Struct::new(42).build(); // Does not compile. 
    let _strct = Struct::<_, &Fn(_)>::new(42).build(); 
} 

Ich mag würde die Art Anmerkung verzichten, wenn die optionalen Felder nicht gesetzt ist, etwa so:

let _strct = Struct::new(42).build(); 

Es soll festgelegt werden, dass der F Typ hängt von T ab.

Ich habe versucht, einen Standard-Typ-Parameter als solche Angabe:

impl<T, F: Fn(T) = Box<Fn(T)>> Struct<T, F> { 

aber das Problem nicht lösen.

Wie kann ich vermeiden, die Typparameter im Struct::new() Aufruf angeben zu müssen?

Wenn es nicht möglich ist, dies zu vermeiden, gibt es Alternativen zum Builder-Muster, die es mir erlauben würden, die Typannotation wegzulassen?

+0

Wenn Sie keinen konkreten Typ für 'T' oder' F' angeben, wie viel Raum sollte dann der Rust-Compiler zum Speichern von 'Struct' reservieren? – Shepmaster

+0

@Shempmaster, in der Theorie, wenn der Compiler bestimmen kann, dass 'F' nie verwendet wird, kann es einfach dieses Feld als Größe 0 schreiben. Das Problem ist natürlich, dass diese Art der vollständigen Programmschlussfolgerung wirklich kompliziert ist. – LinearZoetrope

+0

@Jsor ein interessanter Punkt, aber wie Sie erwähnen, macht Rust keine Inferenz über eine einzige Funktionsgrenze hinaus. – Shepmaster

Antwort

2

Francis Gagné's clever solution Folgen, hier ist eine ähnliche Idee, die auf stabile Rust arbeiten können:

struct Struct<T, F: Fn(T)> { 
    func: Option<F>, 
    value: T, 
} 

enum Structs<T, F: Fn(T)> { 
    Struct1(T), 
    Struct2(T, F), 
} 

impl<T> Struct<T, fn(T)> { 
    fn new(value: T) -> Struct<T, fn(T)> { 
     Struct { 
      func: None, 
      value: value, 
     } 
    } 
} 

impl<T, F: Fn(T)> Struct<T, F> { 
    fn func<F2: Fn(T)>(self, func: F2) -> Struct<T, F2> { 
     Struct { 
      func: Some(func), 
      value: self.value, 
     } 
    } 

    fn build(self) -> Structs<T, F> { 
     use Structs::*; 

     if let Some(func) = self.func { 
      Struct2(self.value, func) 
     } else { 
      Struct1(self.value) 
     } 
    } 
} 

fn main() { 
    let _strct = Struct::new(42) 
     .func(|n| { 
      println!("{}", n); 
     }) 
     .build(); 

    let _strct = Struct::new(42).build(); 
} 

Statt einer klaren Void Typ, sagen wir einfach, dass wir eine Struktur zurück, die für einen Funktionszeiger parametriert werden würde. Sie könnten ebenfalls ein Referenzmerkmal Objekt angeben:

impl<T> Struct<T, &'static Fn(T)> { 
    fn new(value: T) -> Struct<T, &'static Fn(T)> { 

meine eigene Frage aus einem Kommentar beantworten:

Wenn Sie nicht über einen konkreten Typ für T oder F angeben, wie viel Platz sollte die Rust Compiler zu speichern Struct?

Die Größe eines fn() ist 8 Bytes auf einer 64-Bit-Maschine, auf insgesamt 16 Bytes für die gesamte Struktur führt:

std::mem::size_of::<fn()>(); 
std::mem::size_of_val(&strct); 

Wenn Sie jedoch einen konkreten Rückruf geben, Die Struktur benötigt nur 8 Bytes! Das liegt daran, dass der Typ für den Rückruf monomorphisiert wird, der keinen Status benötigt und grundsätzlich inline sein kann.

Die Lösung von Francis Gagné benötigt nur jeweils 8 Byte, da der Typ Void eine Größe von Null hat!

+1

In der Theorie könnte ein 'Struct ' sogar noch weiter optimiert werden: da die' Some' Variante einer 'Option ' nicht instanziiert werden kann, bleibt nur die' None' Variante übrig. Ein Enum mit einer einzelnen Variante benötigt keine Diskriminante (obwohl es augenblicklich scheint, dass es immer noch eine Diskriminante für eine Einheit enum gibt), und da 'None' keine Daten enthält, dann ist die Größe einer 'Option 'könnte Null sein. Somit wäre die Größe eines "Struct " 4 statt 8. –

5

Es gibt eine Möglichkeit, dies zu lösen, indem Sie den Typ des Builders ändern, während er sich entwickelt. Da Struct::func den Builder übernimmt und einen neuen Builder zurückgibt, können wir den Ergebnistyp ändern.

Zuerst müssen wir einen Anfangstyp für F angeben. Wir könnten einfach jede vorhandene Implementierung für Fn(T) auswählen, aber wir können es besser machen. Ich schlage vor, dass wir eine empty/void/uninhabited/bottom type verwenden, so dass es klar ist, dass, wenn F dieser Typ ist, dann die OptionNone ist (Sie können keine Some(x) konstruieren, da es keine gültige x für einen leeren Typ gibt). Ein Nachteil dieses Ansatzes besteht darin, dass die Implementierung von Fn, FnMut und FnOnce für Typen (außer Closures) instabil ist und einen nächtlichen Compiler erfordert.

#![feature(fn_traits)] 
#![feature(unboxed_closures)] 

enum Void {} 

impl<T> FnOnce<T> for Void { 
    type Output =(); 

    extern "rust-call" fn call_once(self, _args: T) { 
     match self {} 
    } 
} 

impl<T> FnMut<T> for Void { 
    extern "rust-call" fn call_mut(&mut self, _args: T) { 
     match *self {} 
    } 
} 

impl<T> Fn<T> for Void { 
    extern "rust-call" fn call(&self, _args: T) { 
     match *self {} 
    } 
} 

Als nächstes wollen wir Struct::new auf einen anderen impl Block verschieben:

impl<T> Struct<T, Void> { 
    fn new(value: T) -> Struct<T, Void> { 
     Struct { 
      func: None, 
      value: value, 
     } 
    } 
} 

Diese impl ist nicht generic auf F: new wird immer nur eine Struct wo F = Void erzeugen. Dies vermeidet Mehrdeutigkeiten in dem Fall, in dem func nie aufgerufen wird.

Schließlich müssen wir func Änderung der Art des Erbauers machen:

impl<T, F0: Fn(T)> Struct<T, F0> { 
    fn func<F1: Fn(T)>(self, func: F1) -> Struct<T, F1> { 
     Struct { 
      func: Some(func), 
      value: self.value, 
     } 
    } 
} 

Diese Methode muss bleiben in einem impl Block, der auf Struct<T, F> über den F Typparameter generisch ist, so dass es sein kann, verwendet auf einem Baumeister, auf dem func bereits genannt wurde. func muss jedoch auch selbst generisch sein, damit es einen beliebigen Funktionstyp erhalten kann (anstelle einer Funktion, die dem Typ des Builders entspricht). Anstatt self zu mutieren, müssen wir dann einen neuen Struct konstruieren, weil wir nicht einfach einen Struct<T, F0> zu einem Struct<T, F1> zwingen können.

Verwandte Themen