Search code examples
genericsrustpolymorphismtrait-objects

Variable parameterised over a trait not a struct?


I'm trying to wrap my head around Rust's generics. I'm writing something to extract HTML from different web sites. What I want is something like this:

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
}

I'm getting an error about the match returning Spiders parameterised over two different types. It expects it to return Spider<GoogleParser> because that's the return type of the first arm of the pattern match.

How can I declare that spider should be any Spider<T: CanGetTitle>?


Solution

  • How can I declare that spider should be any Spider<T: CanGetTitle>?

    You cannot. Simply put, the compiler would have no idea how much space to allocate to store spider on the stack.

    Instead, you will want to use a 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 };
    }