Can't make a nested generic Read or BufRead in a Box

I'm trying to make struct that wraps quick_xml::Reader, with a generic structure inside, which must have BufRead trait.

I need 2 extra structures:

  1. quick_xml::Reader requires BufRead trait,
  2. I want to have a File, Gzip Reader, or Bzip2 reader inside

So the structure has to be like this: a BufReader with dyn Read in a box:

OsmParser<quick_xml::Reader<BufReader<Box<dyn BufRead>>>

But defining it this way doesn't work:

pub struct OsmParser<R: BufRead> {
	rd: Reader<BufReader<Box<R>>>
}
impl <R: BufRead> OsmParser<R> {
	fn new(rd: R) -> OsmParser<Reader<BufReader<Box<R>>>> {
error[E0277]: the trait bound `Reader<BufReader<Box<R>>>: BufRead` is not satisfied
  --> src/main.rs:27:19
   |
27 |     fn new(rd: R) -> OsmParser<Reader<BufReader<Box<R>>>> {
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `BufRead` is not implemented for `Reader<BufReader<Box<R>>>`

Trying to fix it, I got this error, and don't know what to do:

pub struct OsmParser<R: Read> {
	rd: Reader<BufReader<Box<R>>>
}

impl <R: Read> OsmParser<R> {
	fn new(rd: BufReader<Box<R>>) -> OsmParser<R> {
		Self {rd: Reader::from_reader(rd)}
	}

	fn from_path(path: String) -> Result<OsmParser<R>> {
		let rd = {
			...
			match &cap2[2] {
				".gz" => Box::new(GzDecoder::new(fp)?) as Box<dyn Read>,
				".bz2" => Box::new(BzDecoder::new(fp)) as Box<dyn Read>,
				_ => Box::new(fp) as Box<dyn Read>
			}
		};
		let reader = BufReader::new(rd);
		Ok(Self::new(reader))
	}
}
25 | impl <R: Read> OsmParser<R> {
   |       - this type parameter
...
48 |         Ok(Self::new(reader))
   |                      ^^^^^^ expected type parameter `R`, found trait object `dyn std::io::Read`
   |
   = note: expected struct `BufReader<Box<R>>`
              found struct `BufReader<Box<dyn std::io::Read>>`

Tried adding where R: Read to new or from_path just for luck, but it doesn't help.

  1. How do I fix it?
  2. Is this stack of nested types a proper way to do this?

I'm unsure why you get exactly the error you posted. There's probably something weird going on in code you didn't post. Anyways, there are a few things we need to fix:

The BufReader type is used to go from something that implements Read, into something that implements BufRead, so you should do either BufReader<Box<dyn Read>>, or Box<dyn BufRead> without the BufReader.

Those are not the same things! First you say you want a trait object, then you do it with generics. Anyway, the same conceptual issue can be found here. It doesn't make sense to wrap a BufRead in BufReader.

Again, trait objects are not the same as generics. When you use generics, the caller chooses what R is. For example, I might call OsmParser::<TcpStream>::from_path("foo"), and then your method has to return a Result<OsmParser<TcpStream>>. You cannot use a Box<dyn Read> when it has to be a TcpStream, because those are different types. In this case, since the method chooses what the reader type is, you should probably be using trait objects instead of generics.

pub struct OsmParser {
	rd: Reader<BufReader<Box<dyn Read>>>
}

(or this)

pub struct OsmParser {
	rd: Reader<Box<dyn BufRead>>
}
3 Likes

Thanks, this made it work! I see that I didn't get the difference between generic parameter and trait object. Is there any article to read further on this?

Basically, Box<dyn Read> is a single type that can contain any value that implements Read. On the other hand, when you use generics, you are duplicating the code for every type that it is used with. So for example, this:

pub struct OsmParser<R: Read> {
	rd: Reader<BufReader<Box<R>>>
}

impl <R: Read> OsmParser<R> {
	fn new(rd: BufReader<Box<R>>) -> OsmParser<R> {
		Self {rd: Reader::from_reader(rd)}
	}
}

might be converted by the compiler into this:

pub struct OsmParserFile {
	rd: Reader<BufReader<Box<File>>>
}

impl OsmParserFile {
	fn new(rd: BufReader<Box<File>>) -> OsmParserFile {
		Self {rd: Reader::from_reader(rd)}
	}
}


pub struct OsmParserTcpStream {
	rd: Reader<BufReader<Box<TcpStream>>>
}

impl OsmParserTcpStream {
	fn new(rd: BufReader<Box<TcpStream>>) -> OsmParserTcpStream {
		Self {rd: Reader::from_reader(rd)}
	}
}

If the full set of types that OsmParser is used with is File and TcpStream.

In particular, with generics, a OsmParserFile cannot store a TcpStream, and a OsmParserTcpStream cannot store a File. However, with trait objects, the OsmParser type can store both a File and a TcpStream.

3 Likes