2016-12-29 38 views
2

我试图围绕Rust的泛型来包裹我的头。我正在写点东西来从不同的网站提取HTML。我想是这样的:通过特性变量参数不是结构?

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 
} 

我得到一个错误有关参数化了两种不同类型的match返回Spider秒。它期望它返回Spider<GoogleParser>,因为这是模式匹配的第一部分的返回类型。

我该如何声明spider应该是任何Spider<T: CanGetTitle>

回答

3

我该如何声明spider应该是任何Spider<T: CanGetTitle>

你不行。简而言之,编译器不知道要分配多少空间来将spider存储在堆栈上。

相反,你会想使用trait objectBox<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>

只是为了一点点添加到什么@Shepmaster已经说过,spider不能任何Spider<T>,因为它有可能是完全一个Spider<T>。 Rust使用单态化实现了泛型(解释为here),这意味着它为每个使用的具体类型编译一个单独版本的多态函数。如果编译器无法为特定的调用站点推导出唯一的T,那么这是一个编译错误。就你而言,编译器推断该类型必须为Spider<Google>,但随后下一行尝试将其视为Spider<Yahoo>

使用trait对象可让您将所有这些都推迟到运行时。通过将实际对象存储在堆上并使用Box,编译器知道需要多少空间来分配堆栈(只是Box的大小)。但是这带来了性能成本:当需要访问数据时,会有额外的指针间接寻址,更重要的是,优化编译器不能内联虚拟调用。

无论如何,通常可以调整东西,以便您可以使用单形类型。要做到这一点,你的情况的一种方法是,以避免临时分配到多态变量,仅在你知道它的具体类型的地方使用值:

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被调用时,T解决了不同的类型。你只写了一个do_stuff的实现,但是编译器对它进行了二次单元化 - 对于你调用的每种类型都是一次。

如果您使用Box,则每次拨打parser.get_title()时都必须在Boxvtable中查找。但是通过避免查找的需要,这个版本通常会更快,并且允许编译器在每种情况下内联主体parser.get_title()

+0

嗯有趣。我认为在这种情况下,虽然我想要在网站之间做很多共同点,但唯一的区别就是像使用哪个HTML选择器来提取我需要的数据,具体取决于网站等。 – jbrown

+0

*在需要访问数据时需要额外的指针间接引导* =>实际上,这是您支付的最低成本。更高的代价是禁止优化器足够聪明地将该调用虚拟化,这就抑制了内联,这是优化的关键促成因素。因此,虽然额外指针解引用/虚拟调用的成本非常小,但内联和优化的损失(紧密循环中)确实可能非常昂贵。 –

+0

@MatthieuM。谢谢,做了一个调整,使之清楚。 –