Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ? inside closure

Tags:

rust

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?

like image 328
Leśny Rumcajs Avatar asked Sep 14 '25 04:09

Leśny Rumcajs


1 Answers

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.

like image 98
apetranzilla Avatar answered Sep 15 '25 20:09

apetranzilla