Error handling in a validation function


#1

In a current Rust project of mine I writing a compiler (not a classical one but still the best description of what I’m doing). One big part of it is a validator that takes the result of the Parser (an Abstract Syntax Tree) and does a lot of validation checks including type checking but does not transform the tree. The result here is Everything is Ok or a problem was found. Important here is that it does not exhaustively list all problems but aborts when it encounter the first one.

Currently this is done by using a Result type as return value that has a () type as its Ok part. As I do a lot of iterating over vectors and sometimes sets, I use plenty of for loops. Often these loops would be much more concise if I would use iterators and map, filter, all, for_each, … functions on them but I can not do this because I can not use the ? operator inside the closure to abort and return an Error Result, while in for loops I can do that.

I currently thinking about making a rewrite where I get rid of all the options and just panic when I detect an error. as it runs in a separate thread anyway this would be possible without a hassle and i could use a the iterator functions wherever I need.

It has to be noted that the input for this compiler is the output of another compiler, so i never expect an error during validation when the tool chain is used correctly but i still have to check it to protect against attacks that feed corrupted input to the compiler, which under no circumstances is allowed to return anything else than an error.

Readability and maintainability is an important part of this project and as such my question is which approach do you think is better suited and more readable/maintainable: Explicit Result as return or panic on error. I personally start to think that the panic approach could be a good fit here but panic comes with some stigma and thus I’m not really sure.


#2

You can just return result, and write you map() variant that deals with that.

Panics are not good, as they can’t provide any usable information for the caller.


#3

https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.try_fold
might help as well, as it is now stabilized


#4

One thing that I generally use is the fact that collecting Iterator<Item=Result<A,B>> can give you Result<Container<A>, B>. Therefore you can use map onto single Results in order to perform all the iterations if everything is fine, otherwise stopping at the first error and returning it (small dumb example).

Maybe you already know this approach, you looks quite experienced. If you are complaining that this approach is a bit cumbersome respect to the try operator, maybe you are right. On the other hand, it is not easy to think about a syntactic sugar that is not ambiguous (right in the example I a using a map inside a map; if the operator #x would let me return like the try operator but on the outer scope, which is the outer scope? The function or the outer lambda? And what if map calls a normal fn?)


#5

Thanks I did not know about try_fold and try_for_each, they will definitively be useful in many cases.

I did not even consider the idea to build my own iterators that can work with Results seamlessly and i think it will be the best solution to use Results and provide my own small iterator library to handle these.

This would be a useful compromise if a custom try aware iterator implementation turns out to be to complex to do.

Thanks for all the tips and they made me confident to stick with the Result approach and try to solve the problem on the iterator level. It’s my first project in Rust so I’m still new to it and appreciate the tips.


#6

iterr might be of interest.


#7

.collect() can make a Result. It can also collect any number of elements into a single ().

[1,2,3].iter().map(|n| Ok(())).collect::<Result<(), Error>>();

#8

map is, unfortunately, fundamentally a bit awkward. There’s no reasonable way to go from impl Iterator<Item=Result<A,B>> to Result<impl Iterator<Item=A>, B>, because it would need to somehow know whether there was an error (to return Err) before actually looking at the iterator.

Depending what exactly you want to do, there are some scattered helpers, like https://docs.rs/itertools/0.7.8/itertools/trait.Itertools.html#method.map_results