2016-12-29 2 views
2

Ich versuche, Rust Generika um den Kopf zu wickeln. Ich schreibe etwas, um HTML von verschiedenen Websites zu extrahieren. Was ich will, ist so etwas wie dieses:Variable über ein Merkmal parametrisiert keine Struktur?

trait CanGetTitle { 
    fn get_title(&self) -> String; 
} 

struct Spider<T: CanGetTitle> { 
    pub parser: T 
} 

struct GoogleParser; 
impl CanGetTitle for GoogleParser { 
    fn get_title(&self) -> String { 
     "title from H1".to_string().clone() 
    } 
} 

struct YahooParser; 
impl CanGetTitle for YahooParser { 
    fn get_title(&self) -> String { 
     "title from H2".to_string().clone() 
    } 
} 

enum SiteName { 
    Google, 
    Yahoo, 
} 

impl SiteName { 
    fn from_url(url: &str) -> SiteName { 
     SiteName::Google 
    } 
} 

fn main() { 
    let url = "http://www.google.com"; 
    let site_name = SiteName::from_url(&url); 
    let spider: Spider<_> = match site_name { 
     Google => Spider { parser: GoogleParser }, 
     Yahoo => Spider { parser: YahooParser } 
    }; 

    spider.parser.get_title(); // fails 
} 

Ich erhalte eine Fehlermeldung über den match Rückkehr Spider s über zwei verschiedene Arten parametriert. Es erwartet, dass es Spider<GoogleParser> zurückgibt, weil das der Rückgabetyp des ersten Arms der Musterübereinstimmung ist.

Wie kann ich erklären, dass spider eine Spider<T: CanGetTitle> sein sollte?

Antwort

3

Wie kann ich erklären, dass spider eine Spider<T: CanGetTitle> sein sollte?

Sie können nicht. Einfach gesagt, der Compiler würde keine Ahnung haben, wie viel Speicherplatz auf dem Stack zu speichern spider zu reservieren. Box<CanGetTitle>:

impl<T: ?Sized> CanGetTitle for Box<T> 
    where T: CanGetTitle, 
{ 
    fn get_title(&self) -> String { (**self).get_title() } 
} 

fn main() { 
    let innards: Box<CanGetTitle> = match SiteName::Google { 
     SiteName::Google => Box::new(GoogleParser), 
     SiteName::Yahoo => Box::new(YahooParser), 
    }; 
    let spider = Spider { parser: innards }; 
} 
+0

Ich kämpfe immer noch mit diesem. Wird es mit mehreren Eigenschaften arbeiten? Ich brauche Dinge wie 'ParsePage',' GetQuery' usw. und benötige etwas, das ich erweitern kann, um alle Eigenschaften abzudecken, die implementiert werden müssen. – jbrown

+0

@jbrown Warum glaubst du, dass es nicht mit mehreren Eigenschaften funktioniert? – Shepmaster

+0

nur überprüfen. Ich lerne nur Rost. – jbrown

4

Wie kann ich erklären, dass spider jede Spider<T: CanGetTitle> sollte

Stattdessen werden Sie ein trait object verwenden?

Nur ein wenig hinzuzufügen, was @Shepmaster schon gesagt, spider nicht jedeSpider<T> sein kann, weil sie genau einSpider<T> sein muss. Rust implementiert Generics unter Verwendung von Monomorphisierung (erklärt here), was bedeutet, dass es eine separate Version Ihrer polymorphen Funktion für jeden verwendeten Betontyp kompiliert. Wenn der Compiler kein eindeutiges T für eine bestimmte Aufruf-Site ableiten kann, ist es ein Kompilierungsfehler. In Ihrem Fall hat der Compiler abgeleitet, dass der Typ Spider<Google> sein muss, aber dann versucht die nächste Zeile, es als Spider<Yahoo> zu behandeln.

Mit einem Merkmal-Objekt können Sie all dies zur Laufzeit verschieben. Durch das Speichern des tatsächlichen Objekts auf dem Heap und unter Verwendung eines Box weiß der Compiler, wie viel Speicherplatz dem Stack zugewiesen werden muss (nur die Größe eines Box). Dies führt jedoch zu Leistungseinbußen: Es gibt eine zusätzliche Zeigerindirection, wenn auf die Daten zugegriffen werden muss, und vor allem kann der optimierende Compiler keine virtuellen Inline-Aufrufe ausführen.

Es ist oft möglich, Dinge neu zu justieren, so dass Sie trotzdem mit einem monomorphen Typ arbeiten können. Eine Möglichkeit, dass in Ihrem Fall zu tun ist, die temporäre Zuordnung zu einer polymorphen Variablen zu vermeiden, und verwenden Sie den Wert nur an einem Ort, wo man seinen konkreten Typen kennen:

fn do_stuff<T: CanGetTitle>(spider: Spider<T>) { 
    println!("{:?}", spider.parser.get_title()); 
} 

fn main() { 
    let url = "http://www.google.com"; 
    let site_name = SiteName::from_url(&url); 
    match site_name { 
     SiteName::Google => do_stuff(Spider { parser: GoogleParser }), 
     SiteName::Yahoo => do_stuff(Spider { parser: YahooParser }) 
    }; 
} 

Beachten Sie, dass jedes Mal, do_stuff genannt wird, T wird in einen anderen Typ aufgelöst. Sie schreiben nur eine Implementierung von do_stuff, aber der Compiler monomorphisiert sie zweimal - einmal für jeden Typ, mit dem Sie sie aufgerufen haben.

Wenn Sie einen Box verwenden dann jeder Aufruf parser.get_title() wird in der Box ‚s vtable nachgeschlagen werden.Aber diese Version wird normalerweise schneller sein, indem sie die Notwendigkeit für diese Suche vermeidet und dem Compiler die Möglichkeit gibt, den Körper von parser.get_title() in jedem Fall zu inlinern.

+0

Hmm interessant. Ich denke in diesem Fall wird es eine Menge Gemeinsamkeit geben für das, was ich zwischen den Seiten machen möchte, mit den einzigen Unterschieden, wie genau welche HTML-Selektoren verwendet werden müssen, um die benötigten Daten zu extrahieren. – jbrown

+0

* auf Kosten zusätzlicher Zeigerindirection, wenn auf die Daten zugegriffen werden muss * => Das sind tatsächlich die geringsten Kosten, die Sie dafür bezahlen. Die höheren Kosten bestehen darin, dass ein Optimierer, der intelligent genug ist, um den Aufruf zu devirtualisieren, blockiert wird, was Inlining verhindert, was ein Schlüssel für Optimierungen ist. Während die Kosten einer zusätzlichen Zeiger-Dereferenzierung/eines virtuellen Anrufs sehr gering sind, kann der Verlust von Inlining und Optimierungen (in engen Schleifen) tatsächlich sehr kostspielig sein. –

+0

@MatthieuM. Danke, habe einen Tweak gemacht, um das klar zu machen. –

Verwandte Themen