Iterators
The Rust documentation describes an iterator as Composable external iteration.
They're used a lot in idiomatic Rust code on collection types (slice, Vec, HashMap, and so on) so it's very important to learn to master them. This code will allow us to have a nice introduction. Let's look at the code now:
slice.iter().map(|highscore| highscore.to_string()).
collect::<Vec<String>>().join(" ")
This is quite difficult to read and understand for the moment, so let's rewrite it as follows:
slice.iter() .map(|highscore| highscore.to_string()) .collect::<Vec<String>>() .join(" ")
Better (or at least more readable!). Now let's go step by step, as follows:
slice.iter()
Here, we create an iterator from our slice. A really important and fundamental thing to note about iterators in Rust; they're lazy. Creating an iterator doesn't cost anything more than the size of the type (generally a structure containing a pointer and an index). Until the next() method is called, nothing happens.
So now we have an iterator, awesome! Let's check the next step:
.map(|highscore| highscore.to_string())
We call the iterator's map method. What it does is simple: it converts the current type into another one. So here, we convert a u32 into a String.
Really important to note: at this point, the iterator still hasn't done anything. Keep in mind that nothing is done until the next() method is called!
.collect::<Vec<String>>()
And now we call the collect() method. It'll call the next() method of our iterator as long as it didn't get all elements and store them into a Vec. This is where the map() method will be called on every element of our iterator.
And finally the last step:
.join(" ")
This method (as its name indicates) joins all the elements of the Vec into a String separated by the given &str (so, " " in our case).
Finally, if we give &[1, 14, 5] to the slice_to_string function, it'll return a String containing "1 14 5". Pretty convenient, right?
If you want to go a bit deeper with the iterators, you can take a look at the blog post at https://blog.guillaume-gomez.fr/articles/2017-03-09+Little+tour+of+multiple+iterators+implementation+in+Rust or directly take a look at the iterator official documentation at https://doc.rust-lang.org/stable/std/iter/index.html.
It's time to go back to our saving function:
fn save_highscores_and_lines(highscores: &[u32],
number_of_lines: &[u32]) -> bool { let s_highscores = slice_to_string(highscores); let s_number_of_lines = slice_to_string(number_of_lines); write_into_file(format!("{}\n{}\n", s_highscores,
s_number_of_lines), "scores.txt").is_ok() }
Once we have converted our slices to String, we write them into the scores.txt file. The is_ok() method call just informs the caller of the save_highscores_and_lines() function if everything has been saved as expected or not.
Now that we can save scores, it'd be nice to be able to get them back when the tetris game is starting!