Use `?` on `Option` in function that returns `Result<_, E>` when you know how to make a `E` in case of `None`

Hello! I have a function that returns a Result<T, E>, and inside that function i have quite a bunch of Iterator::next calls which return an Option. I know how to construct a E from a None. Is there a way to do the following:

fn function(iter: &mut Peekable<impl Iterator<Item = Input>>) -> Result<Output, Error> {
    // the function code needs this item, so getting a None is an error
    let next_item: Input = iter.next()?;

    Ok(next_item as Output)
}

instead of the more lengthy .ok_or(...)?? This would help remove a lot of boilerplate in my code and will improve readability. Example on playground (not my actual code, which is a little more complicated): Rust Playground

I'm on nightly, so if the solution requires unstable features i don't mind at all. Thanks in advance!

You have to use NoneError to convert None to your error type: Reddit - Dive into anything

It looks like that type was removed after 1.53: NoneError in std::option - Rust

You can factor it out into another function and then ok_or on that one.

fn function(iter: &mut Peekable<impl Iterator<Item = Input>>) -> Result<Output, Error> {
    let mut inner = || {
        let next_item: Input = iter.next()?;
        Some(next_item as Output)
    };

    inner().ok_or(Error::UnexpectedIteratorEnd)
}
3 Likes

Sounds exactly like what i want, but it looks like it was removed: `#[cfg(bootstrap)]` out `NoneError` and other v1 try_trait stuff by scottmcm · Pull Request #85482 · rust-lang/rust · GitHub

Yeah nope, that looks even worse haha. Thanks anyway!

You could make your own method that returns a Result.

trait NextResult: Iterator {
    fn next_result(&mut self) -> Result<Self::Item, Error> {
        self.next().ok_or(Error::UnexpectedIteratorEnd)
    }
}

impl<T: Iterator> NextResult for T {}

fn function(iter: &mut Peekable<impl Iterator<Item = Input>>) -> Result<Output, Error> {
    let next_item: Input = iter.next_result()?;
    Ok(next_item as Output)
}
1 Like

Oops, sorry :sweat_smile: . Anyway, I've heard that there are ongoing efforts to make the Try operator more interoperable, so I guess that the lang team is pursuing other ways to make the conversions that don't involve special language constructs.

Note that it was an intentional design decision to not allow ? on an Option to work in a Result-returning function. That was considered an advantage of the 3058-try-trait-v2 - The Rust RFC Book proposal.

If you make your own type you can choose to allow it to interoperate in certain ways with certain other types, but mixing Option and Result is intentionally not supported.

Using .ok()? or .ok_or(MeaningfulError)? is the intended flow.

2 Likes

On second thought, I think this is slightly more sustainable.

impl From<()> for Error {
    fn from(_: ()) -> Self {
        Self::UnexpectedIteratorEnd
    }
}

trait NextResult: Iterator {
    fn next_result(&mut self) -> Result<Self::Item, ()> {
        self.next().ok_or(())
    }
}

impl<T: Iterator> NextResult for T {}

fn function(iter: &mut Peekable<impl Iterator<Item = Input>>) -> Result<Output, Error> {
    let next_item: Input = iter.next_result()?;
    Ok(next_item as Output)
}

It'll let you use NextResult for other errors. But don't do this if your error type is public.

1 Like

Thanks, crazy how i could not think of this myself! Here's my final solution to avoid XKCD 979:

use std::iter::Peekable;

use crate::error::Error;

pub trait PeekableIterarator: Iterator {
    fn peek(&mut self) -> Option<&Self::Item>;
    fn peek_mut(&mut self) -> Option<&mut Self::Item>;
}

impl<I: Iterator> PeekableIterarator for Peekable<I> {
    fn peek(&mut self) -> Option<&Self::Item> {
        self.peek()
    }

    fn peek_mut(&mut self) -> Option<&mut Self::Item> {
        self.peek_mut()
    }
}

pub trait ResultIterator: PeekableIterarator {
    fn next_ok(&mut self) -> Result<Self::Item, Error> {
        self.next().ok_or(Error::UnexpectedEof)
    }

    fn peek_ok(&mut self) -> Result<&Self::Item, Error> {
        self.peek().ok_or(Error::UnexpectedEof)
    }

    fn peek_mut_ok(&mut self) -> Result<&mut Self::Item, Error> {
        self.peek_mut().ok_or(Error::UnexpectedEof)
    }
}

And then when i want to use ? i use these functions instead of the normal ones. I'll mark this as the solution, but full credit to you, thanks again!

1 Like

Good to know, thanks. While this looks like an arbitrary limitation to me, i can understand this choice in terms of language design.

Why would you put a () there? How about

struct UnexpectedIteratorEnd;
trait NextResult: Iterator {
    fn next_result(&mut self) -> Result<Self::Item, UnexpectedIteratorEnd> {
        self.next().ok_or(UnexpectedIteratorEnd)
    }
}

Because Result<_, ()> is an anti-pattern, in my experience.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.