Reputation: 7065
I'm trying to write a function which takes a slice of numbers and calculates the mean.
I tried using the ideas from Implementing mean function for generic types but get an error.
My code is:
extern crate num;
use num::{FromPrimitive, Zero};
use std::ops::{Add, Div};
fn main() {
let mut numbers = [10, -21, 15, 20, 18, 14, 18];
let err = "Slice is empty.";
println!("Mean is {:.3}", mean(&numbers).expect(err));
}
fn mean<T>(numbers: &[T]) -> Option<f64>
where
T: Copy + Zero + Add<T, Output = T> + Div<T, Output = T> + FromPrimitive,
{
match numbers.len() {
0 => None,
_ => {
let sum = numbers.iter().sum: ();
let length = FromPrimitive::from_usize(numbers.len()).unwrap();
Some(sum / length)
}
}
}
The error is:
error[E0658]: type ascription is experimental (see issue #23416)
--> src/main.rs:20:23
|
20 | let sum = numbers.iter().sum: ();
| ^^^^^^^^^^^^^^^^^^^^^^
Is there any way of writing a generic mean function without using experimental features?
Upvotes: 2
Views: 1884
Reputation: 8803
You are doing 2 different operations in your generic function:
Sum<T>
boundary to your generic type parameter.f64
or any float type that you want to limit. Since you are using num crate i added ToPrimitive
as boundary which tells that your generic type can be converted to a primitive type. Here is the implementation :
fn mean<'a, T: 'a>(numbers: &'a [T]) -> Option<f64>
where
T: ToPrimitive + Sum<&'a T>,
{
match numbers.len() {
0 => None,
_ => {
let sum = numbers.iter().sum::<T>();
let length = f64::from_usize(numbers.len())?;
T::to_f64(&sum).map(|sum| sum / length)
}
}
}
Upvotes: 7
Reputation: 7065
How about this:
use std::iter::Sum;
fn main() {
let err = "Slice is empty.";
// Test vector of integers
let numbers = vec![10i32, -21, 15, 20, 18, 14, 18];
println!("Mean is {:.3}", mean(numbers.into_iter()).expect(err));
// Test vector of floating point numbers
let numbers = vec![10f64, -21f64, 15f64, 20f64, 18f64, 14f64, 18f64];
println!("Mean is {:.3}", mean(numbers.into_iter()).expect(err));
// Test empty vector
let numbers: Vec<i32> = Vec::new();
println!("Mean is {:.3}", mean(numbers.into_iter()).expect(err));
}
fn mean<T, I: Iterator<Item = T>>(iter: I) -> Option<f64>
where
T: Into<f64> + Sum<T>,
{
let mut len = 0;
let sum = iter
.map(|t| {
len += 1;
t
})
.sum::<T>();
match len {
0 => None,
_ => Some(sum.into() / len as f64)
}
}
Same code in the Rust Playground
It seems to have the following advantages over the answers posted so far:
num
crate.FromPrimitive
and Zero
.Or this version which has the following differences to the one above:
use std::iter::Sum;
fn main() {
let err = "Slice is empty.";
// Test aray of integers
let numbers = [10, -21, 15, 20, 18, 14, 18];
println!("Mean is {:.3}", mean(numbers.iter()).expect(err));
// Test array of floating point numbers
let numbers = [10f64, -21f64, 15f64, 20f64, 18f64, 14f64, 18f64];
println!("Mean is {:.3}", mean(numbers.iter()).expect(err));
// Test empty array
let numbers: [i32; 0] = [];
match mean(numbers.iter()) {
Some(mean_) => println!("Mean is {:.3}", mean_),
None => println!("Empty array"),
}
}
fn mean<'a, T, I>(iter: I) -> Option<f64>
where
T: Into<f64> + Sum<&'a T> + 'a,
I: Iterator<Item = &'a T>,
{
let mut len = 0;
let sum = iter
.map(|t| {
len += 1;
t
})
.sum::<T>();
match len {
0 => None,
_ => Some(sum.into() / len as f64),
}
}
Thanks to my friend Sven for code contribution.
Upvotes: 1
Reputation: 58805
The other answers will likely help you with your real problem of writing this function generically.
The actual error you've asked about though is just a syntax mistake. You wrote this:
let sum = numbers.iter().sum: ();
But almost certainly intended to write:
let sum = numbers.iter().sum();
The compiler has seen the :
that you have accidentally included, and thinks that you are trying to use type ascription. Type ascription is syntax to use type annotations inline within an expression, instead of just in variable declarations.
What you wrote is very similar to:
let sum: () = numbers.iter().sum;
If you were to enable type ascription in a nightly rustc build, the error would change because now the compiler will tell you that sum
is a function and definitely does not have type ()
.
Upvotes: 3
Reputation: 3215
When the compiler can't figure out the type S
of fn sum<S>(self) -> S
, you either need to write let foo: Bar = baz.sum();
or let foo = baz.sum::<Bar>();
If you are sure that T
will always be some type of number primitive, you ought to collect a owned type from sum()
with let sum: T = numbers.iter().cloned().sum();
and add the core::iter::Sum
bound to T
. Otherwise, you may want to work with references.
You can make your function a little more generic be returning Option<T>
but if you really want to return Option<f64>
, you should cast T
to f64
using the ToPrimitive
trait. Like this.
Upvotes: 0