sleb
1
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]);
}
}
pvdrz
3
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
Hyeonu
5
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
system
Closed
7
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.