How to work with generic structs

I'm building a TrueType font decoder, and I have the following struct:

struct TtfDecoder<R> {
    reader: R,
}

Now I want to create a new instance for it using the following code:

impl<R: BufRead> TtfDecoder<R> {
    fn new(reader: &[u8]) -> Self {
        let r = BufReader::new(reader);
        Self { reader: r }
    }
}

But it fails with the message:

33 | impl<R: BufRead> TtfDecoder<R> {
   |      - this type parameter
...
36 |         Self { reader: r }
   |                        ^ expected type parameter `R`, found struct `std::io::BufReader`

What's wrong here?

When you write a struct like, TtfDecode<R>, it means that R can be any type at all. When you define the impl block like impl<R: BufRead> TtfDecoder<R>, it means that R is any type that implements the BufRead trait. Note the word any. While the variable r is of type std::io::BufReader and hence compatible with R, it may not be the type specified by the caller of TtfDecoder::new. Note that the generic type is decided by the caller of the function. What you probably want is:

impl<R: BufRead> TtfDecoder<R> {
    fn new(reader: R) -> Self {
        Self { reader }
    }
}

You can then call it as let decoder = TtfDecoder::new(std::io::BufReader::new(bytes));.

3 Likes

In cases like that it's usually good idea to explain what you expected to achieve.

Because it's very easy to say “what's wrong” (compiler did that pretty convincingly) but very hard to understand what you wanted to achieve without extra info.

Maybe this:

pub struct TtfDecoder<R: Read> {
    reader: BufReader<R>,
}

impl<R: Read> TtfDecoder<R> {
    fn new(reader: R) -> Self {
        let r = BufReader::new(reader);
        Self { reader: r }
    }
}

Try to explain what you want to achieve in plain English and then, 9 times out of 10, it would be easy to implement what you invented.

Just showing us something uncompileable written in Rust doesn't really help much: compiler can not userstand what you wrote, how can you be sure other people would be able to understand your intent?

3 Likes

Type parameters are outside of your control. The specific type of R will be chosen by the user of the struct, not by you, so you can't say R is a BufReader, because that would be a bug for TtfDecoder<File>, for example. Note that R is a single specific, but unknown at this point, type. It's not the same as Box<dyn Read> which allows multiple types with the same interface.

You can have reader: BufReader<R>. Or you can move new to impl block for TtfDecoder<BufReader<R>>.