Looking forward to the day I can solve something simple in Rust on my own in less than a half hour without help. I want to take a vector of Student instances and find the top three students in terms of their average score. I'm not able to determine from the error message what type it wants. Once that is solved, it will panic and I don't understand that either.
Compiling playground v0.0.1 (/playground)
error[E0277]: a value of type `&[Student]` cannot be built from an iterator over elements of type `&Student`
--> src/main.rs:34:60
|
34 | let top_students: &[Student] = students.iter().take(3).collect();
| ^^^^^^^ value of type `&[Student]` cannot be built from `std::iter::Iterator<Item=&Student>`
|
= help: the trait `FromIterator<&Student>` is not implemented for `&[Student]`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`
To learn more, run the command again with --verbose.
There's a few reasonable choices for that line. Taking @alice's suggestion leads to this (with #[derive(Clone)] on Student:
let top_students: Vec<Student> = students.iter().take(3).cloned().collect();
Without the clone, you can get a Vec<&Student>:
let top_students: Vec<&Student> = students.iter().take(3).collect();
But my suggestion in this case is to use indexing instead of an iterator. The results are already contiguous in memory, so you don't need another allocation:
let top_students: &[Student] = &students[0..3];
Your panic comes from here:
fn average(numbers: &[i8]) -> f32 {
numbers.iter().sum::<i8>() as f32 / numbers.len() as f32
}
i8 can only hold values between -128 and 127, so it isn't big enough to hold the sum of 3 grades. You need to cast the grades to a larger type before trying to sum them:
numbers.iter().map(|&x| x as f32).sum::<f32>() / numbers.len() as f32
Mapping over the i8 values to convert them to f32 seems like too much work and not something that would have occurred to me. I wonder if in terms of code understandability, the following would be preferred in the average function. I guess it's a matter of personal preference. Is one approach considered more idiomatic than the other?
let mut sum: f32 = 0.0;
for n in numbers {
sum += *n as f32;
}
sum / numbers.len() as f32
It's mostly a matter of personal preference. Restricting average to calculating with i8s feels a little off to me. If this was part of a medium-large project, I'd consider writing it like this (or with an equivalent loop):
fn average(numbers: &[impl Copy+Into<f32>]) -> f32 {
let total = numbers.iter().copied().map(|x| x.into()).sum::<f32>();
total / numbers.len() as f32
}
Or even:
fn average(numbers: impl ExactSizeIterator<Item=impl Into<f32>>) -> f32 {
let len = numbers.len() as f32;
let total = numbers.map(|x| x.into()).sum::<f32>();
total / len
}
I realy like that you can do things like that in Rust too, it works well with type inference. I was using similar codes in Python, e.g map(str.lower, ["Aa", "BB"]) instead of map(lambda i: i.lower(), ["Aa", "BB"])