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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With