I’m working on a parser that parses a packet-based stream. Some of the packets are containers. A container also holds packets. In fact, a container can hold any valid packet. For instance, a compressed data packet could hold a compressed data packet! (This probably isn’t a good idea, but for other containers, this is actually sensible.)
To parse the packets, I’m passing around a reference to an object implementing the
std::io::Read trait. Sometimes, I add a filter, like in the compression case. Since the contents of a container are a valid message, after creating the filter, I just call the main deserialize function again.
So, my API looks like:
fn deserialize<T: Read>(bio: &mut T) -> Result<T>; fn compressed_data_body<T: Read>(bio: &mut T) -> Result<T>; ...
The practical result of this arrangement is that rustc refuses to compile my code:
reached the recursion limit while instantiating `deserialize::<flate2::read::DeflateDecoder<&mut flate2::read::DeflateDecoder<&mut flate2::read::DeflateDecoder<&mut flate2::read::DeflateDecoder<&mut flate2::read::DeflateDecoder<...
This isn’t surprising: deserialize calls compressed_data_body and vice versa, and compressed_data_body can extend it’s generic parameter.
I haven’t figure out how to communicate a base case to the compiler (although I do have one at run-time: the maximum recursion depth is 16-levels), and, unfortunately, I suspect that there is no way to do so.
One way to break this logjam is to Box all of the readers. But, this is ugly and I can’t do any introspection, which is occasionally helpful.
Since the interface is public, I’d like to preserve an idiomatic interface as much as possible.
A small example illustrating my problem is here: https://play.rust-lang.org/?gist=27b4e3f2630ae5d76c9d70f2b97f864a&version=stable