Help with Result and `?`

Hi All,

I'm (obviously) very new to Rust. I'm trying to parse a string value containing a list of ranges. These are inclusive and can be open or closed. Example "1-3,5 " should result in [1..=3, 5..=5]. I was able to do a hacky version that just panicked whenever it ran into a problem. Now I'm trying to evolve that to use Result. The problem is that I'm getting compiler errors wherever I'm using the ?. Example:

the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)

I can't figure out why since parse_positions() does return a Result. Can anyone help?

code:

use core::fmt;
use std::ops::RangeInclusive;

#[derive(Debug)]
struct PositionParseError {
    message: String,
}

impl fmt::Display for PositionParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

type PositionList = Vec<RangeInclusive<u32>>;

fn parse_range_u32(s: &str) -> Result<u32, PositionParseError> {
    s.parse().or(Err(PositionParseError{message: format!("Couldn't parse `{}` as range position", s)}))
}

fn parse_positions(s: &str) -> Result<PositionList, PositionParseError> {
    let result: PositionList = s
        .split(|c| c == ',' || c == ' ')
        .map(|range_spec| {
            let range_parts: Vec<&str> = range_spec.split("-").collect();
            match range_parts.as_slice() {
                ["", end] => {
                    let end = parse_range_u32(end)?;
                    1..=end
                },
                [start, ""] => {
                    let start = parse_range_u32(start)?;
                    start..=std::u32::MAX
                },
                [start, end] => {
                    let start = parse_range_u32(start)?;
                    let end = parse_range_u32(end)?;
                    start..=end
                },
                [start] => {
                    let start = parse_range_u32(start)?;
                    start..=start
                },
                _ => return Err(PositionParseError{message:format!("Couldn't parse `{}` as range", range_spec)})
            }
        })
        .collect();

    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_single_position() {
        assert_eq!(parse_positions("1").unwrap(), vec![1u32..=1u32]);
    }

    #[test]
    fn test_parse_comma_separated_positions() {
        assert_eq!(
            parse_positions("1,2").unwrap(),
            vec![1u32..=1u32, 2u32..=2u32]
        );
    }

    #[test]
    fn test_parse_space_separated_positions() {
        assert_eq!(
            parse_positions("1 2").unwrap(),
            vec![1u32..=1u32, 2u32..=2u32]
        );
    }

    #[test]
    fn test_parse_range_positions() {
        assert_eq!(parse_positions("1-2").unwrap(), vec![1u32..=2u32]);
    }

    #[test]
    fn test_parse_open_left_range_positions() {
        assert_eq!(parse_positions("-3").unwrap(), vec![1u32..=3u32]);
    }

    #[test]
    fn test_parse_open_right_range_positions() {
        assert_eq!(parse_positions("7-").unwrap(), vec![7u32..=std::u32::MAX]);
    }
}

this may help

This is happening because those ? are inside the closure of the .map(), which confuses Rust because you're returning ranges inside that closure.

One solution would be to create a vector instead and push each range using a for loop where you can use ? because you're still in the same function.

Other solution would be to return something like Ok(range) inside map and then collect it into a Result<PositionList>.

3 Likes

To explain, please note that map takes a closure. This is a function. Because of this, using ? in it will result in the closure itself returning a Result<_, _>, so your .collect statement is more like a Vec<Result<_, _>>, so what you probably want to do is something like the following, according to .collect's docs:

s
    .split(|c| c == ',' || c == ' ')
    .map(|range_spec| {
        let range_parts: Vec<&str> = range_spec.split("-").collect();
        match range_parts.as_slice() {
            ["", end] => {
                let end = parse_range_u32(end)?;
                Ok(1..=end)
            },
            //.. Other cases
            _ => Err(...)
        }
    })
    .collect()

Because it will automatically yield an Err(e) in the case it encounters one while trying to collect.


Edit: Following @Hyeonu's suggestions below.

2 Likes

Small addition to the @OptimisticPeach 's nice answer, try omitting the variable result entirely as the return type is already specified in the function signature.

1 Like

Here is a variant on @OptimisticPeach's closure solution, regarding the positionning of the wrapping Ok(...) (this is just a matter of taste / aesthetics):

type PositionList = Vec<RangeInclusive<u32>>;
fn parse_positions (s: &'_ str) -> Result<PositionList, PositionParseError>
{
    s   .split(|c| c == ',' || c == ' ')
        .map(|range_spec| Ok({
            let range_parts: Vec<&str> = range_spec.split("-").collect();
            match range_parts.as_slice() {
                ["", end] => {
                    let end = parse_range_u32(end)?;
                    1 ..= end
                },
                [start, ""] => {
                    let start = parse_range_u32(start)?;
                    start ..= ::std::u32::MAX
                },
                [start, end] => {
                    let start = parse_range_u32(start)?;
                    let end = parse_range_u32(end)?;
                    start ..= end
                },
                [start] => {
                    let start = parse_range_u32(start)?;
                    start ..= start
                },
                _ => return Err(PositionParseError {
                    message: format!("Couldn't parse `{}` as range", range_spec),
                }),
            }
        }))
        .collect()
}

As you can see, you can wrap the whole closure's body within a Ok(..), and then use ? or return Err(..) when wanting to return an error;

  • (and then let .collect transform an Iterator yielding Result<Item, Err>
    into a Result<Collection<Item>, Err>>, like with @OptimisticPeach's solution).

Here this Result<Collection...> is exactly the value that you intend to return, but if it weren't (or if you wanted to do some post-processing before returning), you could apply the ? operator again after .collect(), to extract the inner Collection<Item>:

type Item = ...;
type Error = ...;

fn bar () -> Result<Item, Error>
{
    ...
}

fn foo (iterator: impl Iterator) -> Result< Vec<Item>, Error >
{
    let vec: Vec<Item> =
        iterator
            .map(|x| -> Result<Item, Error> {Ok({
                let item = bar()?;
                ...
                item
            })})
            .collect()?;
    ...
    Ok(vec)
}
1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.