Reputation: 38958
I have a String
with colon-separated values. Each sub-string should be a key in a map. I want to write a function that converts the String
into a Vec
of map values, or a failure if any key is not present.
My attempt so far is incomplete, but I'm taking it in small steps. The first step is to convert the string into a vector of optional u32
s. (This may not the best approach):
fn parse(string: &String, lookup: HashMap<String, u32, FastHasher>) -> Vec<Option<u32>> {
string.split(":").map(|s: &str| lookup.get(s)).collect()
}
This results in
error: the trait bound `std::vec::Vec<std::option::Option<u32>>: std::iter::FromIterator<std::option::Option<&u32>>` is not satisfied [--explain E0277]
--> <anon>:8:52
|>
8 |> string.split(":").map(|s: &str| lookup.get(s)).collect()
|> ^^^^^^^
note: a collection of type `std::vec::Vec<std::option::Option<u32>>` cannot be built from an iterator over elements of type `std::option::Option<&u32>`
I believe this means I need to import or write my own from-iterator behaviour for Option<&u32>
, right?
After I've done that, how can I wrap in an Ok
or Err
depending upon the presence of any None
s?
Upvotes: 1
Views: 351
Reputation: 10776
If you really want to return the references, you should specify lifetimes explicitly:
pub fn parse<'a, 'b>(string: &'a str, lookup: &'b HashMap<String, u32>) -> Vec<Option<&'b u32>> {
string.split(":").map(|s| lookup.get(s)).collect()
}
Regarding the second part of the question about the conversion to Result
type:
And after I've done that, how can I wrap in an
Ok
orErr
depending upon the presence of anyNone
s?
This can be done by folding and accumulating the results into Ok<Vec<u32>>
. Following example illustrates the idea. See Rust Playground runnable example.
use std::collections::HashMap;
use std::result::Result;
#[derive(Debug)]
pub struct ParseError {
key: String,
}
impl ParseError {
fn new(k: &str) -> ParseError {
ParseError { key: k.to_owned() }
}
}
fn parse(string: &str, lookup: &HashMap<String, u32>) -> Result<Vec<u32>, ParseError> {
string.split(":")
.fold(Ok(Vec::new()), |res, s| {
let mut vec = try!(res);
match lookup.get(s) {
Some(&v) => {
vec.push(v);
Ok(vec)
}
None => Err(ParseError::new(s)),
}
})
}
Upvotes: 1
Reputation: 88536
The HashMap::get()
method returns an optional reference to the value inside of the map. So you have an iterator over Option<&u32>
, but you want to have an iterator over Option<u32>
. This is done by saying:
lookup.get(s).cloned()
// ^^^^^^^^^
This makes your current code compile.
To answer the question in your title: there is a neat little FromIterator
impl:
impl<A, V> FromIterator<Option<A>> for Option<V> where V: FromIterator<A>
This means, that, for example, you can collect an iterator over items of types Option<u32>
into Option<Vec<u32>>
. This is exactly what you want! So just change your return type:
fn parse(string: &str, lookup: &HashMap<String, u32, FastHasher>) -> Option<Vec<u32>> {
// ^^^^^^^^^^^^^^^^
string.split(":").map(|s| lookup.get(s).cloned()).collect()
}
You can try the working code here on playground.
Also note the following, question-independent changes I made:
string
argument is &str
now instead of &String
. There is practically no reason for ever passing &String
instead of &str
, so the latter is preferred for being more generic.HashMap
, as your function does not need to own it.Upvotes: 6
Reputation: 22163
In order to avoid this issue, instead of Vec<Option<&u32>>
you could just return Vec<Option<u32>>
:
fn parse(string: &String, lookup: HashMap<String, u32>) -> Vec<Option<u32>> {
string.split(":").map(|s: &str| if let Some(&e) = lookup.get(s) { Some(e) } else { None }).collect()
}
Which, as Matthieu suggested, can be simplified to:
string.split(":").map(|s: &str| lookup.get(s).cloned()).collect()
I'm not sure if it adds value if you wrap it in a Result
; you can easily check for None
afterwards:
let v = vec![Some(1), None, Some(3)];
println!("{:?}", v.contains(&None));
Upvotes: 3