Rust generics syntax for mean function

2019-08-02 03:29发布

问题:

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?

回答1:

Your type also needs to implement Sum<T>, so you need to add the Sum bound too. This is not enough, you also need to convert your generic type to a f64. The num crate also has the ToPrimitive trait to do this:

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>();
            FromPrimitive::from_usize(numbers.len())
                .and_then(|length: f64| T::to_f64(&sum).and_then(|val| Some(val / length)))
        }
    }
}

Playground



回答2:

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 ().



回答3:

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:

  1. Much simpler generic type definition.
  2. No reliance on external num crate.
  3. No need for difficult-to-guess traits like FromPrimitive and Zero.
  4. No manual lifetimes declarations.

Or this version which has the following differences to the one above:

  1. Can take arrays rather than vectors.
  2. Does not consume the array (or vector).
  3. Needs manual lifetime declarations.
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.



回答4:

  • 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.