These are chat archives for rust-lang/rust

16th
May 2017
bigDaddyS1oth
@bigDaddyS1oth
May 16 2017 06:03
Hey folks, came up with this error while trying to quickstart the hello_world example from the Rocket repo.
error[E0554]: #[feature] may not be used on the stable release channel --> /home/bigdaddydeadpoo1/.cargo/registry/src/github.com-1ecc6299db9ec823/pear_codegen-0.0.8/src/lib.rs:1:1 | 1 | #![feature(plugin_registrar, rustc_private, quote)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Joonas Koivunen
@koivunej
May 16 2017 06:36
@bigDaddyS1oth it'd looks like you are trying to use rocket with stable version of rust ("on the stable release channel")
@bigDaddyS1oth on the rocket.rs github page it says "Rocket is web framework for Rust (nightly)" which means it requires you to use latest rust version to compile programs based on it. could be more clear though...
@bigDaddyS1oth if you installed rust using rustup it is easy to install nightly channel as well, rustup install nightly and then you need to enable it in your project directory, please see https://github.com/rust-lang-nursery/rustup.rs#working-with-nightly-rust for more
Fra ns
@snarf95_twitter
May 16 2017 07:56
is this a good idea?
Joonas Koivunen
@koivunej
May 16 2017 08:05
@snarf95_twitter i don't know about the test suite in that task, but yeah that looks sufficient and readable
Denis Lisov
@tanriol
May 16 2017 08:05
I'd probably use .filter(|&c| c.is_ascii() && c.is_lowercase())
Joonas Koivunen
@koivunej
May 16 2017 08:07
@snarf95_twitter hashset could be replaced with a bool array, ascii letters being the indices but it's probably too much of a hassle
Denis Lisov
@tanriol
May 16 2017 08:08
@snarf95_twitter However, both versions are actually not quite correct yet :-)
Fra ns
@snarf95_twitter
May 16 2017 08:10
some guy did it like this
with 'bit magic'
Denis Lisov
@tanriol
May 16 2017 08:11
Speaking more precise, that depends on the definitions... is "the quİck brown fox jumps over the lazy dog" a pangram?
Fra ns
@snarf95_twitter
May 16 2017 08:11
yeah?
Denis Lisov
@tanriol
May 16 2017 08:12
Note that letter is dotted capital I, which is non-ASCII, but becomes normal "i" when lowercased.
Ilya Bogdanov
@vitvakatu
May 16 2017 08:13
what about space symbol?
Fra ns
@snarf95_twitter
May 16 2017 08:13
ohh... yeah then no
space is ignored
Denis Lisov
@tanriol
May 16 2017 08:13
So you may want to use to_ascii_lowercase instead of to_lowercase
Fra ns
@snarf95_twitter
May 16 2017 08:14
okay thanks :)
Denis Lisov
@tanriol
May 16 2017 08:17
Would be also nice to use is_ascii_lowercase for filtering, but it's not stable yet :-(
Fra ns
@snarf95_twitter
May 16 2017 08:59
Is ascii lowercase could be implemented like c==to_ascii_lowercase(c) no?
Denis Lisov
@tanriol
May 16 2017 09:09
No, this one will also return true for anything non-ASCII.
However, c.is_ascii() && c.is_lowercase() is almost as readable.
Nikolay Denev
@ndenev
May 16 2017 09:27

Hello! I wonder if anyone has suggestion for cases like this:

error[E0308]: mismatched types
  --> src/profiler/mongo_profile_records.rs:47:53
   |
47 |             Bson::UtcDatetime(dt) => Ok(UtcDateTime(dt)),
   |                                                     ^^ expected struct `chrono::DateTime`, found struct `chrono::datetime::DateTime`
   |
   = note: expected type `chrono::DateTime<chrono::UTC>`
              found type `chrono::datetime::DateTime<chrono::offset::utc::UTC>`
   = help: here are some functions which might fulfill your needs:
           - .timezone()

error: aborting due to previous error

It seems like Bson::UtcDatetime is using chrono::datetime::DateTime, but even if I import it I still get this conflict, though it seems they are the same strucs, just one is re-exported from the crate root.

Sergey Noskov
@Albibek
May 16 2017 09:30
@ndenev it usually happens when other crate's dependency version and version in your crate mismatch
Nikolay Denev
@ndenev
May 16 2017 09:31
hmm, that might be the case. It looks a bit confusing
Nikolay Denev
@ndenev
May 16 2017 09:37
@Albibek thanks for the hint, that was it. I've downgraded my chrono dependency to the same version as the one that the bson crate was pulling and it just compiled. phew...
Fra ns
@snarf95_twitter
May 16 2017 09:51
Could the error message be made more clear in cases like this?
I had the same problem once and couldn't figure out what was wrong
Nikolay Denev
@ndenev
May 16 2017 09:54
Maybe it would hint from where each type is coming from. Looking at it now it looks like that they can even have exactly the same signature and still have the same error which is going to be even more confusing. Imagine expected struct `chrono::DateTime`, found struct `chrono::DateTime`
Sherzod Mutalov
@shmutalov
May 16 2017 09:55
Maybe there are different version of chrono libraries?
Nikolay Denev
@ndenev
May 16 2017 09:57
@shmutalov yes, this was the problem. I had in my Cargo.toml a dependency on chrono = "0.3", while bson is using 0.2.25. The thing is that it's not very obvious from the error.
Andrew Thorburn
@ipsi
May 16 2017 10:23
Maybe an extra help, like:
   = help: here are some functions which might fulfill your needs:
           - .timezone()
   = help: the types can be found in the following files:
           - chrono::DateTime           => /Users/llama/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.3/src/...
           - chrono::UTC                => /Users/llama/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.3/src/...
           - chrono::datetime::DateTime => /Users/llama/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.2.25/src/...
           - chrono::offset::utc::UTC   => /Users/llama/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.2.25/src/...
Sherzod Mutalov
@shmutalov
May 16 2017 10:24
@ipsi is your error log from real build log?
Andrew Thorburn
@ipsi
May 16 2017 10:24
Nope
Fra ns
@snarf95_twitter
May 16 2017 11:06
Yes please
Fra ns
@snarf95_twitter
May 16 2017 11:23
Curious if there are any rust-powered repos that need help and are newbie-friendly?
Denis Lisov
@tanriol
May 16 2017 11:24
@snarf95_twitter A good starting point is TWiR's call for participation section: https://this-week-in-rust.org/blog/2017/05/09/this-week-in-rust-181/#call-for-participation
Wesley Gahr
@voider1
May 16 2017 11:26
image.png
Does anyone understand these errors?
fn post_message(&self, path: &[&str], params: &[(&str, &str)]) -> Result<Message> {
    debug!("Posting message...");
    let url = ::construct_api_url(&self.bot_url, path);
    self.client.post(&url).form(&params).send().and_then(|d| {

        let rjson: Value = check_for_error(d.json()?)?;
        let message_json = rjson.get("result").ok_or(JsonNotFound)?;
        serde_json::from_value(message_json.clone())?
    })?
}
From this code.
Fra ns
@snarf95_twitter
May 16 2017 11:29
thx @tanriol definitely going to have a look
Andrew Thorburn
@ipsi
May 16 2017 11:30
@voider1 What's the type of Error in Result<Message>? I suspect that you don't have a from implementation for reqwest::Error for whatever that error type is.
e.g., if you have a custom type MyError:
impl From<reqwest::Error> for MyError {
    fn from(err: reqwest::Error) -> MyError { ... }
}
Wesley Gahr
@voider1
May 16 2017 11:30
@ipsi That's the weird part, I do have that implementation.
image.png
Andrew Thorburn
@ipsi
May 16 2017 11:32
That is very peculiar. Maybe you're having the same issue as a previous poster with different versions of the same library on your path?
Wesley Gahr
@voider1
May 16 2017 11:33
How can I check?
Btw, I have another version of this method which does work.
    /// The actual networking done for sending messages.
fn post_message(&self, path: &[&str], params: &[(&str, &str)]) -> Result<Message> {
    debug!("Posting message...");
    let url = ::construct_api_url(&self.bot_url, path);
    let mut data = self.client.post(&url).form(&params).send()?;
    let rjson: Value = check_for_error(data.json()?)?;
    let message_json = rjson.get("result").ok_or(JsonNotFound)?;
    let message: Message = serde_json::from_value(message_json.clone())?;
    Ok(message)
}
This works fine.
I like the one using and_then better.
Any ideas?
Denis Lisov
@tanriol
May 16 2017 11:36
Try adding .map_err(From::from) before and_then
Wesley Gahr
@voider1
May 16 2017 11:37
image.png
/// The actual networking done for sending messages.
fn post_message(&self, path: &[&str], params: &[(&str, &str)]) -> Result<Message> {
    debug!("Posting message...");
    let url = ::construct_api_url(&self.bot_url, path);
    self.client.post(&url).form(&params).send().map_err(From::from).and_then(|d| {
        let rjson: Value = check_for_error(d.json()?)?;
        let message_json = rjson.get("result").ok_or(JsonNotFound)?;
        serde_json::from_value(message_json.clone())
    })?
}
Denis Lisov
@tanriol
May 16 2017 11:38
And if .map_err(Error::from) instead?
Wesley Gahr
@voider1
May 16 2017 11:39
Error from std or my module?
Denis Lisov
@tanriol
May 16 2017 11:39
Your.
Wesley Gahr
@voider1
May 16 2017 11:40
/// The actual networking done for sending messages.
fn post_message(&self, path: &[&str], params: &[(&str, &str)]) -> Result<Message> {
    debug!("Posting message...");
    let url = ::construct_api_url(&self.bot_url, path);
    self.client.post(&url).form(&params).send().map_err(Error::from).and_then(|d| {
        let rjson: Value = check_for_error(d.json()?)?;
        let message_json = rjson.get("result").ok_or(JsonNotFound)?;
        serde_json::from_value(message_json.clone())
    })?
}
image.png
Almost.
Denis Lisov
@tanriol
May 16 2017 11:41
Ok, and one more: serde_json::from_value(message_json.clone()).map_err(Error::from)
Wesley Gahr
@voider1
May 16 2017 11:42
image.png
Shame that I need this much overhead to accomplish it.
So, what's going wrong now?
Denis Lisov
@tanriol
May 16 2017 11:47
Hmm... yeah, is the final question mark operator not needed at all?
Wesley Gahr
@voider1
May 16 2017 11:48
That fixed it.
Denis Lisov
@tanriol
May 16 2017 11:48
Threading Results and Errors requires some care, yes. At least it does not explode on runtime :-)
Wesley Gahr
@voider1
May 16 2017 11:48
So can I make this easier on the eyes or is there no more compact way?
/// The actual networking done for sending messages.
fn post_message(&self, path: &[&str], params: &[(&str, &str)]) -> Result<Message> {
    debug!("Posting message...");
    let url = ::construct_api_url(&self.bot_url, path);
    self.client.post(&url).form(&params).send().map_err(Error::from).and_then(|mut d| {
        let rjson: Value = check_for_error(d.json()?)?;
        let message_json = rjson.get("result").ok_or(JsonNotFound)?;
        serde_json::from_value(message_json.clone()).map_err(Error::from)
    })
}
/// The actual networking done for sending messages.
fn post_message(&self, path: &[&str], params: &[(&str, &str)]) -> Result<Message> {
    debug!("Posting message...");
    let url = ::construct_api_url(&self.bot_url, path);
    self.client
        .post(&url)
        .form(&params)
        .send()
        .map_err(Error::from)
        .and_then(|mut d| {
                      let rjson: Value = check_for_error(d.json()?)?;
                      let message_json = rjson.get("result").ok_or(JsonNotFound)?;
                      serde_json::from_value(message_json.clone()).map_err(Error::from)
                  })
}
RustFmt makes this a lot uglier.
Denis Lisov
@tanriol
May 16 2017 11:53
IMO, in this case the version without and_then looked better.
Wesley Gahr
@voider1
May 16 2017 11:53
Yeah...
It does.
Sad.
It adds too much overhead.
Fra ns
@snarf95_twitter
May 16 2017 11:57
you could like split the code? up until send is put in a var
Wesley Gahr
@voider1
May 16 2017 12:00
@snarf95_twitter What do you mean?
Fra ns
@snarf95_twitter
May 16 2017 12:05
let result = self.client.(....).send()
result.map_err(...)
Fra ns
@snarf95_twitter
May 16 2017 12:57
why can't I do step_by() on an iter?
Zakarum
@omni-viral
May 16 2017 12:58
@snarf95_twitter implement it )
Sherzod Mutalov
@shmutalov
May 16 2017 13:00
Hey, чуваки, I've wrote Gitter API client gitter-rs in Rust for you.
Zakarum
@omni-viral
May 16 2017 13:01
@snarf95_twitter
struct StepIter<I> { iter: I, step: usize }
impl<I> Iterator for StepIter<I> where I: Iterator {
    type Item = I::Item;
    fn next(&mut self) -> Option<Self::Item> {
        self.iter.nth(self.step)
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}
Jonas Platte
@jplatte
May 16 2017 13:02
@voider1 If you don't like the visual indentation rustfmt does by default, try putting this into your crate root as .rustfmt.toml or rustfmt.toml.
(and make sure your rustfmt is up to date, you can update with cargo install -f rustfmt, assuming you installed it with cargo the first time)
@snarf95_twitter Again, the answer is itertools: https://docs.rs/itertools/*/itertools/trait.Itertools.html#method.step
Fra ns
@snarf95_twitter
May 16 2017 13:05
omg itertools should be included in rust right now
Jonas Platte
@jplatte
May 16 2017 13:08
@snarf95_twitter Well, Rusts standard library is deliberately not "batteries included"
Zakarum
@omni-viral
May 16 2017 13:08
@jplatte it looks more like “not all kinds of batteries included"
Jonas Platte
@jplatte
May 16 2017 13:08
But it has been made very easy to pull in crates, so IMHO it's really not a problem
@SCareAngel Yeah there are a few things in std that might as well be outside of it, but most of it has a good reason to be in there.
Zakarum
@omni-viral
May 16 2017 13:12
@jplatte I’m agree. It would be a mess if many crates defines it’s own Optional
Actually I miss Void in std
Jonas Platte
@jplatte
May 16 2017 13:15
@SCareAngel Void? You mean the type that can't be instantiated? It is in the language, as !.
Although on stable it's still some kind of weird language feature thing that's not actually a proper type (there is a nightly feature that fixes that)
Zakarum
@omni-viral
May 16 2017 13:16
@jplatte Can I do this?
let x: Result<u32, !> = Ok(32);
Jonas Platte
@jplatte
May 16 2017 13:17
You probably still need to enable the feature, let me see if I can find the PR..
@SCareAngel Yeah, your code should work on nightly with #![feature(never_type)].
Tracking issue is rust-lang/rust#35121
Zakarum
@omni-viral
May 16 2017 13:19
https://is.gd/jetvQ8
Got an syntax error
Jonas Platte
@jplatte
May 16 2017 13:20
Yeah because you wrote = Result instead of : Result :D
Zakarum
@omni-viral
May 16 2017 13:20
@jplatte Silly me )
Yeap. That works fine
Fra ns
@snarf95_twitter
May 16 2017 13:21
how can I reuse this iterator?
    let digits_iter = input.chars().rev().filter_map(|c| c.to_digit(10));

    if digits_iter.count() <= 1 {
        return false;
    }
    let sum: u32 = digits_iter
        .step(2)
        .map(|c| c * c)
        .map(|c| if c > 9 { c - 9 } else { c })
        .chain(digits_iter.skip(1).step(2))
        .sum();
    println!("{}", sum);
    (sum % 10 == 0)
Zakarum
@omni-viral
May 16 2017 13:22
Now we only need special methods for Result<T, !> and Result<!, E>
And such
@snarf95_twitter maybe you can use size_hint insted of count
of even len in case input: ExactSizeIterator
or not. Cause there is filter_map
Fra ns
@snarf95_twitter
May 16 2017 13:24
I'm more interested in not rewriting the let digits_iter = input.chars().rev().filter_map(|c| c.to_digit(10))all the time
error[E0382]: use of moved value:digits_iter``
Jonas Platte
@jplatte
May 16 2017 13:25
@snarf95_twitter You might be able to clone it.
Fra ns
@snarf95_twitter
May 16 2017 13:26
no method namedclonefound for typestd::iter::FilterMap<std::iter::Rev<std::str::Chars<'_>>`
Zakarum
@omni-viral
May 16 2017 13:26
@snarf95_twitter use cloned then
Jonas Platte
@jplatte
May 16 2017 13:26
Huh? weird...
Zakarum
@omni-viral
May 16 2017 13:26
nothing weird if input: !Clone
Jonas Platte
@jplatte
May 16 2017 13:26
@snarf95_twitter It might be because of the closure..? But that's hard to say without the full error message.
Fra ns
@snarf95_twitter
May 16 2017 13:27
with cloned: error[E0271]: type mismatch resolving <[closure@src\lib.rs:10:54: 10:72] as std::ops::FnOnce<(char,)>>::Output == std:: option::Option<&_>
Jonas Platte
@jplatte
May 16 2017 13:27
@SCareAngel input seems like it would be a &str or String here, and the chars() method on str returns an unconditionally clone-able iterator.
Fra ns
@snarf95_twitter
May 16 2017 13:27
it's a &str ys
Jonas Platte
@jplatte
May 16 2017 13:28
@snarf95_twitter Could you maybe just put a full example up on https://play.rust-lang.org/ and give us a shortened link so we can have a look at the full error message?
Fra ns
@snarf95_twitter
May 16 2017 13:29
sure
Jonas Platte
@jplatte
May 16 2017 13:30
oh right, you use itertools...
Zakarum
@omni-viral
May 16 2017 13:30
can’t find crate itertools
Jonas Platte
@jplatte
May 16 2017 13:30
I think the other rust playground supports a few external crates though
Fra ns
@snarf95_twitter
May 16 2017 13:30
yeah what to do?
Jonas Platte
@jplatte
May 16 2017 13:30
just a sec...
other playground works :)
Zakarum
@omni-viral
May 16 2017 13:32
yeap
Jonas Platte
@jplatte
May 16 2017 13:32
all hail @shepmaster :D
Zakarum
@omni-viral
May 16 2017 13:32
but closure isn’t clonable
but why?
rust-lang/rfcs#1369
Jonas Platte
@jplatte
May 16 2017 13:36
It doesn't work with an fn either ^^°
So basically the impl Clone for FilterMap<...> where ... is useless D:
Zakarum
@omni-viral
May 16 2017 13:37
It could be done if you use not a closure but some special clonable type which implements Fn(…)->
But that’ll make your code gross
Jonas Platte
@jplatte
May 16 2017 13:38
Yeah but that seems like nobody really ever does it
Now I wonder why I never hit this myself / saw anybody else hit it before
Zakarum
@omni-viral
May 16 2017 13:38
lol. this again rust-lang/rust#28229
Jonas Platte
@jplatte
May 16 2017 13:39
Seems like a very annoying issue, with no obvious workaround
@SCareAngel Oh wow, yeah that's an interesting bug :D
I guess it kind of makes sense given the information from the other bug report (that Clone is not a lang item, in contrast to Copy) but I would not have expected there to be automatic impls of Copy that break language rules (ignoring the supertrait requirement)
Heh, and someone managed to make the compiler ICE because of this
Like this
Fra ns
@snarf95_twitter
May 16 2017 13:43
what can I do though?
Jonas Platte
@jplatte
May 16 2017 13:44
@snarf95_twitter I think I have an idea for a workaround, wait a sec
Zakarum
@omni-viral
May 16 2017 13:44
I had one. But Rc<T> does not implement Fn when T: Fn
Jonas Platte
@jplatte
May 16 2017 13:46
@snarf95_twitter So this works (compiles, at least), but might look slightly weird: http://play.integer32.com/?gist=53d988bf8958607085a6357793c1120c&version=undefined
Is it clear what I did there / why and how it works?
Fra ns
@snarf95_twitter
May 16 2017 13:48
yes I think that makes sense, it's basically like a local function returning a new iterator every time you call digits_iter()
thanks :)
Jonas Platte
@jplatte
May 16 2017 13:48
Exactly :)
Zakarum
@omni-viral
May 16 2017 13:48
I can suggest replacing digits_iter().count() <= 1 with digites_iter().take(2).count() <= 1
Fra ns
@snarf95_twitter
May 16 2017 13:49
hah yeah good idea :)
now I just gotta figure out why tests are failing
Jonas Platte
@jplatte
May 16 2017 13:51
Hmm, you could also do if let Some(_) = digits_iter().next().and_then(Iterator::next) :D
or if digits_iter.next().and_then(Iterator::next).is_some()
Zakarum
@omni-viral
May 16 2017 13:52
@jplatte but opposite
Jonas Platte
@jplatte
May 16 2017 13:52
Oh, right
is_none then
Denis Lisov
@tanriol
May 16 2017 14:04
@snarf95_twitter Because you need to double, not square, the even digits?
Fra ns
@snarf95_twitter
May 16 2017 14:04
yeah I just found out lol... but it is still failling
" 5 9 " becomes 14... which it should right? so why is the test failling
I'm suspecting faulty test-suite here
Denis Lisov
@tanriol
May 16 2017 14:06
It should be 13, shouldn't it?
Sorry, made a mistake.
Fra ns
@snarf95_twitter
May 16 2017 14:07
9 is doubled (9*2=18) which is gt than 9 so it becomes (18-9) => 9
then 5 is just added so 9 +5 = 14
but tests require that 9+5 %10 == 0
Denis Lisov
@tanriol
May 16 2017 14:08
It's actually 5 that should be doubled, no?
Fra ns
@snarf95_twitter
May 16 2017 14:08
no it starts from the rightmost digit
ok you were right
Fra ns
@snarf95_twitter
May 16 2017 14:14
had misread that I guess thought it started from rightmost then skipped 1 but instead it skips 1 from right
like this
Andrew Thorburn
@ipsi
May 16 2017 14:48
Is there a reason type aliases can't be used for traits? It would save me quite a bit of boilerplate right now...
Jonas Platte
@jplatte
May 16 2017 14:49
@ipsi Well traits and types are very much different things. There is an RFC for trait aliases, but it will take a while until that is merged, and then implemented, and then stabilized.
The common workaround is implementing a subtrait that is automatically implemented for every type that implements the traits / trait + bounds you want to alias.
Andrew Thorburn
@ipsi
May 16 2017 14:50
Ah yeah, that would work.
Jonas Platte
@jplatte
May 16 2017 14:51
Oh, actually, rust-lang/rfcs#1733 has been merged three weeks ago. But it's not implemented yet.
Tracking issue: rust-lang/rust#41517
Andrew Thorburn
@ipsi
May 16 2017 14:51
I think I'm just still adjusting from Java-land, so trait == interface == type.
Awesome. Thanks for the links.
Jonas Platte
@jplatte
May 16 2017 14:52
Hm, does Java have type aliases? I don't think I've ever seen that. (I haven't used Java a lot)
Andrew Thorburn
@ipsi
May 16 2017 14:53
Oh no, that would be far too useful - more replying to your point that traits != types in Rust.
Jonas Platte
@jplatte
May 16 2017 14:54
Haha, okay ^^
Actually traits can be used in type position, but then you get trait objects, which is the same thing you have in Java (calling methods does dynamic dispatch)
And of course trait object types can't be used as bounds on functions, because they aren't traits.
Plus you usually don't want dynamic dispatch in Rust if you can get around it.
Andrew Thorburn
@ipsi
May 16 2017 15:06
Pretty much - the consequence of that tends to be generics everywhere, which can get a bit repetitive.
Fra ns
@snarf95_twitter
May 16 2017 16:51
what about an iterator that chunks data given some predicate? like take_while but instead of ending iterator it should continue with next chunk
there's group_by from itertools
Fra ns
@snarf95_twitter
May 16 2017 18:06
    let word_groups = input
        .chars()
        .group_by(|&c| c.is_alphanumeric())
        .into_iter()
        .map(|(k, g)| g)
        .filter(|&g| g.any(|c| c.is_alphanumeric()));
cannot borrow immutable local variable g as mutable
Fra ns
@snarf95_twitter
May 16 2017 18:52
this is how you count words... sadly couldn't get the map/filter thing working no matter what I did... cloned, peekable etc.
Denis Lisov
@tanriol
May 16 2017 20:14
You can do this
Fra ns
@snarf95_twitter
May 16 2017 20:31
Very cool. I had tried with cloned() but not collect in map. Using your solution one could even do a fold using a mut hashmap to avoid the for and let. But I don't know about readability:)