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返回s 的错误。Spider它期望它返回Spider<GoogleParser>,因为这是模式匹配的第一臂的返回类型。

我怎么能声明spider应该是 any Spider<T: CanGetTitle>

4

2 回答 2

4

我怎么能声明spider应该是 any Spider<T: CanGetTitle>

只是在@Shepmaster 已经说过的内容中添加一点,spider不能是any Spider<T>,因为它必须完全是one Spider<T>Rust 使用单态化(在此处解释)实现泛型,这意味着它为使用的每个具体类型编译一个单独的多态函数版本。如果编译器无法推断出T特定调用站点的唯一性,那么这是一个编译错误。在您的情况下,编译器推断该类型必须为Spider<Google>,但下一行尝试将其视为Spider<Yahoo>

使用 trait 对象可以让您将所有这些都推迟到运行时。通过将实际对象存储在堆上并使用 a Box,编译器知道需要分配多少堆栈空间(只是 a 的大小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,但编译器将其单态化两次 - 对于您调用它的每种类型一次。

如果您使用 aBox那么每次调用都必须在的vtableparser.get_title()中查找。但是这个版本通常会更快,因为它避免了查找的需要,并允许编译器在每种情况下内联正文的可能性。Boxparser.get_title()

于 2016-12-29T21:36:11.267 回答
3

我怎么能声明spider应该是 any Spider<T: CanGetTitle>

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

相反,您将需要使用trait 对象: 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 };
}
于 2016-12-29T16:49:17.353 回答