1

I've got this simple parsing function

use std::collections::BTreeMap;

fn parse_kv(data: &str) -> BTreeMap<String, String> {
    data.split('&')
        .map(|kv| kv.split('='))
        .map(|mut kv| (kv.next().unwrap().into(), kv.next().unwrap().into()))
        .collect()
}

#[test]
fn parse_kv_test() {
    let result = parse_kv("test1=1&test2=2");
    assert_eq!(result["test1"], "1");
    assert_eq!(result["test2"], "2");
}

It works fine and all, but I want to have Option or Result return type like so:

fn parse_kv(data: &str) -> Option<BTreeMap<String, String>>

This implementation:

fn parse_kv(data: &str) -> Option<BTreeMap<String, String>> {
    Some(data.split('&')
        .map(|kv| kv.split('='))
        .map(|mut kv| (kv.next()?.into(), kv.next()?.into()))
        .collect())
}

Unfortunately gives the following error:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
  --> src/ecb_cut_paste.rs:23:24
   |
23 |         .map(|mut kv| (kv.next()?.into(), kv.next()?.into()))
   |                        ^^^^^^^^^^ cannot use the `?` operator in a function that returns `(_, _)`
   |
   = help: the trait `std::ops::Try` is not implemented for `(_, _)`
   = note: required by `std::ops::Try::from_error`

Is it somehow possible to use ? operator inside closure to return None from such function? If not, how would I need to handle idiomatically such case?

Leśny Rumcajs
  • 2,259
  • 2
  • 17
  • 33
  • Possible duplicate of [How do I stop iteration and return an error when Iterator::map returns a Result::Err?](https://stackoverflow.com/questions/26368288/how-do-i-stop-iteration-and-return-an-error-when-iteratormap-returns-a-result) – E_net4 Mar 05 '19 at 20:44
  • 1
    Not really, because I'm chaining other functions after the `?` operator. – Leśny Rumcajs Mar 05 '19 at 21:02
  • 2
    I don't agree it is a duplicate (though definitely related). For one thing, the `?` operator didn't exist when that other question was posted and answered. Plus, from a fairly quick look I'm struggling to see how to apply the ideas from the other question to this one easily. I think the community could benefit from having this question answered anew. – Jarak Mar 05 '19 at 21:11

1 Answers1

6

The issue here is that the closure itself is a function, so using ? will return from the closure instead of the outer function. This can still be used to implement the function the way you want, however:

use std::collections::BTreeMap;

fn parse_kv(data: &str) -> Option<BTreeMap<String, String>> {
    data.split('&')
        .map(|kv| kv.split('='))
        .map(|mut kv| Some((kv.next()?.into(), kv.next()?.into())))
        .collect()
}

#[test]
fn parse_kv_test() {
    let result = parse_kv("test1=1&test2=2").unwrap();
    assert_eq!(result["test1"], "1");
    assert_eq!(result["test2"], "2");

    let result2 = parse_kv("test1=1&test2");
    assert_eq!(result2, None);
}

There are a couple points to note here: First, the question marks and Some(...) in the second map invocation mean you have an iterator of Option<(String, String)> - type inference figures this out for you.

The next point of note is that collect() can automatically convert Iterator<Option<T>> into Option<Collection<T>> (same with Result - relevant documentation here). I added a test demonstrating that this works.

One other thing to be aware of is that using collect in this way still allows short-circuiting. Once the first None is yielded by the iterator, collect will immediately return with None, rather than continuing to process each element.

apetranzilla
  • 5,331
  • 27
  • 34