2016-12-29 7 views
2

私はRustのジェネリックの周りに頭を包み込みたい。私は別のWebサイトからHTMLを抽出するためのものを書いています。私は2つの異なるタイプの上にパラメータ化match返すSpider秒程度の誤差を取得しています変数は構造体ではなく特性にパラメータ化されていますか?

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 
} 

:私が欲しいのはこのようなものです。それはSpider<GoogleParser>を返すことを期待しています。これは、それがパターンマッチの最初の腕の戻り値の型だからです。

spiderSpider<T: CanGetTitle>である必要があります。

答えて

3

spiderSpider<T: CanGetTitle>とするにはどうすればよいですか?

できません。簡単に言えば、コンパイラはどれくらいのスペースを割り当てて、スタックにspiderを格納するか分かりません。

代わりに、あなたはtrait objectを使用したいと思うでしょう: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

私はまだこれで苦労しています。それは複数の形質でも機能しますか? 'ParsePage'や' GetQuery'などが必要になり、実装が必要なすべての特性をカバーするために拡張できるものが必要になります。 – jbrown

+0

@jbrownなぜあなたはそれが複数の形質で機能しないと信じていますか? – Shepmaster

+0

チェックしてください。私は錆を学んでいるだけです。 – jbrown

4

がどのように私はspiderはどのSpider<T: CanGetTitle>であることを宣言することができますか?

ちょうどそれが正確に Spider<T>なければならないのでspiderは、任意のSpider<T>することはできません、@Shepmasterはすでに言ったことに少しを追加します。 Rustは、使用されている具体的な型ごとに別のバージョンの多型関数をコンパイルすることを意味するmonomorphization(here)を使用してジェネリックを実装します。コンパイラが特定の呼び出しサイトに対して一意のTを推測できない場合、コンパイルエラーです。あなたのケースでは、コンパイラは型がSpider<Google>でなければならないと推測しましたが、次の行はそれをSpider<Yahoo>として扱います。

traitオブジェクトを使用すると、そのすべてを実行時に遅延させることができます。実際のオブジェクトをヒープに格納し、Boxを使用することによって、コンパイラはスタック割り当てにどれだけのスペースが必要であるかを知っています(Boxのサイズ)。しかし、これにはパフォーマンスコストが伴います。データにアクセスする必要があるときに余分なポインタ間接指定があり、さらに重要なことは、最適化コンパイラが仮想呼び出しをインライン化できないことです。

とにかくモノモーフ型で作業できるように、しばしば物事を再吟味することは可能です。 、T

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 }) 
    }; 
} 

お知らせたびdo_stuffが呼び出されます:あなたのケースでそれを行うための一つの方法は、多型変数への一時的な割り当てを避けるため、そして唯一のあなたは、その具体的な種類を知っている場所で値を使用することです別のタイプに解決されます。 do_stuffの1つの実装しか記述しませんが、コンパイラはそれを2回呼びます。

あなたがBoxを使用する場合は、parser.get_title()への各呼び出しはBoxvtableで検索する必要があります。しかし、このバージョンは通常、そのルックアップの必要性を回避し、それぞれの場合にparser.get_title()の本体をインライン化する可能性をコンパイラに許可することで、より高速になります。

+0

興味深いことに私は、このサイトでは、サイトによって必要なデータを抽出するためにどのHTMLセレクタを使うかのような唯一の違いがあり、サイト間で何をしたいのかについて多くの共通性があると思います。 – jbrown

+0

*データにアクセスする必要があるときに余計なポインタ間接を犠牲にして* =>実際には、それはあなたが支払うコストの中で最も少なくなります。コストが高くなると、オプティマイザがコールを非仮想化するのに十分スマートなので、最適化の重要なイネーブラであるインライン化が禁止されます。余分なポインタ逆参照/仮想呼び出しのコストは非常に小さいものの、インライン化と最適化の喪失は(きついループで)実際には非常に高価になります。 @MatthieuM。 –

+0

。ありがとう、それを明確にするために微調整を行いました。 –

関連する問題