This came up in How to return a nom Parser<_, _, _> from a function? but I think it's a sufficiently general situation that merits its own thread.
I'm currently learning Rust and I've noticed a situation coming up a few times, which is when a function that I am calling returns a
Result<..., err> and the
err type contains a borrowed value nested somewhere within it.
In the linked example, I have something like this
let input = std::fs::read_to_string("data/input.2")?;
pub fn mk_parser<'a>() -> impl Parser<&'a str, MyThing, nom::error::Error<&'a str>>
.parse(&input) on this
Parser returns me an
pub type IResult<I, O, E = error::Error<I>> = Result<(I, O), Err<E>>;
Most importantly, note that the
E in this error type contains a nested
&str which is borrowed, ultimately, from the
input: String defined on the first line. So we cannot return this error from this function.
The only way I can figure out how to turn the error into an owned value, which I can then
Try with the
? syntax, is to pretty much render the entire error to a String.
But that seems like a huge hack... surely there must be something better than this? Am I holding it wrong or is there something much better that I can be doing here?
This whole topic about "should the return type be a complex domain specific ADT or a simple string" comes up in every language and the answer is usually to map middleware errors into your app's relevant error containers (which almost always end up being roughly "retryable", "fatal" and "can be fixed by interacting with a user"). But Rust brings this extra complication where the error type might not be allowed to escape a particular scope because of ownership rules, in which case it forces us to do any handling of that domain specific error type at this layer which is very restrictive because often times we may want to factor that handling into its own module. Imagine if
main were actually a function that took the filename as input, it would mean that we MUST handle the domain specific error (either mapping it into our own ADT or rendering it) here because we can't return it as it is. And that must be done in every function that hits the same problem, instead of being able to do it in a catch-all error handler as is common in other languages (most notably that's very common in Haskell and FP Scala, but I've even seen it done in Java and golang).
There is a
nom specific way of turning the
&str into a
String and make it owned by calling
mk_parser().parse(&input).map_err(|e| e.to_owned())?; ... but this might not be available in the general case where some other library returns a borrowed value in its error.
What's an idiomatic way to deal with this when it happens? I feel like perhaps it would be good practice for crates to only return owned values in the error type to avoid restricting the caller in these cases, but that won't always be possible for performance reasons and whatnot which is what I assume
nom has gone for.
I don't want to get into the situation that for every crate I end up having to create copy/paste ADTs of their error types with owned values in them, and writing convertors. That'd allow the app error handling to still be done centrally but it is excessive and very brittle against upgrades in crates.