Idiomatic way to consume an iterator that can fail

I have a byte stream that represents a message header. I represent that as an iterator over u8.

I then have a Headers enum.

What is the most idiomatic way to consume the iterator and produce the Headers enum when consuming the stream can fail. I've come across three solutions, but I'm not sure what the most common solution is.

1. Just create a method
This would not involve implementing any traits. Just create a read_stream(iterator) method that returns a result. Simple enough, but I'd like to use the type conversion traits in Rust. It feels more idiomatic.

2. Implement FromIterator
This is also straightforward, but I see that trait is used to create collections and things, not often to create a single "chunk" of a datatype like an instance of an enum.

Also, more importantly, from_iter() does not return a result, and panicing on a malformed stream is not acceptable.

3. Implement TryFrom
This was my first solution, and it works. But then I can't actually use an iterator. I have to collect that iterator into a vector or something, which I'd really rather not do.

I'd really like to be able to go straight from an iterator to a Result with the Enum or a well-defined error, keeping all the parsing logic in the enum implementation.

Id appreciate any thoughts. Thanks!

You could create a wrapper type around a Result and implement FromIterator on it instead, I suppose.

struct ParsedHeader(Result<Header, HeaderParseError>);

But I'm not really sure there's an advantage to doing that over just using a function.

What I would call "the idiomatic way to consume an iterator that can fail" is already handled: If you implement FromIterator<Item> for T, then you automatically get FromIterator<Result<Item, E>> for Result<T, E>.

But in your case, it's not the iteration that is fallible [1], it's the construction of the Header.

Presumably you say you can't actually use an iterator with TryFrom because its existing blanket implementations conflict with an implementation for any iterator over u8. That is an unfortunate property of TryFrom. If your iterator is always the same type (or just a few possible types), you could implement on that.

You could also implement on a local newtype that just wraps any other type, but the ergonomics and other gains pretty much vanish at that point, so I'd probably just go with an associated function (perhaps even called try_from) at that point.


  1. or if it is, you have already chosen to hide this by (presumably) just ending iteration on errors, as your items are u8s ↩ī¸Ž

5 Likes

Thank you both for your perspectives.

But in your case, it's not the iteration that is fallible , it's the construction of the Header.

Yeah, that's a better way of thinking about it.

You could create a wrapper type around a Result and implement FromIterator on it instead

I agree that's a little extra boiler plate for no real reason.

So, I've decided just to create a Header::try_from_stream(&stream);', which is working well.

Thanks!

This is the method I'd normally recommend.

The FromIterator trait doesn't give you a nice way to return an error, and semantically this wouldn't make much sense. The FromIterator trait is mainly meant to be used in conjunction with Iterator::collect() to collect a number of items into some collection (e.g. Vec), whereas you are trying to read a handful of bytes from a stream.

Similarly, the TryFrom trait is intended for fallible conversions, but I'd argue that reading bytes from a stream and parsing them isn't really a "conversion". There is also the annoying bit about consuming the stream or being forced to collect all bytes into a vector, which isn't ideal/practical.

On the other hand, if you use a bespoke constructor method, you get to define your own meaning to the method without being tied down by people's existing assumptions about how it works or the constraints of the trait signature.

1 Like