【问题标题】:Rust generics syntax for mean function均值函数的 Rust 泛型语法
【发布时间】:2019-01-13 11:23:45
【问题描述】:

我正在尝试编写一个函数,该函数接受一段数字并计算平均值。

我尝试使用来自 Implementing mean function for generic types 的想法,但出现错误。

我的代码是:

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)
        }
    }
}

错误是:

error[E0658]: type ascription is experimental (see issue #23416)
  --> src/main.rs:20:23
   |
20 |             let sum = numbers.iter().sum: ();
   |                       ^^^^^^^^^^^^^^^^^^^^^^

有没有什么方法可以在不使用实验特性的情况下编写通用的均值函数?

【问题讨论】:

  • 你链接的问题不建议写sum: ()。这在今天的 Rust 中不是有效的语法,我只能假设它是一个错字
  • 尝试自己推断lengthsum的类型。你会明白为什么编译器不能做到这一点。剧透:可能有很多类型实现了num::FromPrimitive。并且可能有许多类型实现了std::iter::Sum&lt;&amp;T&gt;。就像可能有很多类型实现了std::ops::Div&lt;_, Output = f64&gt;(这里的_是因为无法推断length的类型)。
  • 要使推理起作用,您必须确保没有歧义。

标签: generics math syntax rust


【解决方案1】:

您正在通用函数中执行 2 种不同的操作:

  • 对 slice 中的所有值求和:您需要通过将 Sum&lt;T&gt; 边界添加到泛型类型参数来告诉您的元素是可求和的。
  • 2 个元素的除法运算: 您的泛型类型需要转换为f64 或您想要限制的任何浮点类型。由于您使用的是 num crate 我添加了 ToPrimitive 作为边界,它告诉您的泛型类型可以转换为原始类型。

这里是实现:

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)
        }
    }
}

Playground

【讨论】:

  • 非常感谢。多年使用 Python 后,我对这些特征如此迅速地变得如此复杂感到惊讶。我永远不会知道我需要来自第三方库的 Zero 和 ToPrimitive 之类的特征来做一些简单(看似)的事情,比如取一些数字的平均值。
  • @blokeley 取一些数字的平均值——这就是“问题”:你没有取“一些数字”的平均值。您正在采用可以表达某些属性集的任何泛型类型的平均值。在 Python 中,您可以将字符串集合传递给等效函数并获得运行时错误。等效的 Rust 代码强制您在编译代码时所做的任何事情都是有效的。您可以创建自己的 自己的 trait 并仅为数字实现它,以减少此函数的明显复杂性。
【解决方案2】:

其他答案可能会帮助您解决通用编写此函数的真正问题。


您询问的实际错误只是语法错误。你写了这个:

let sum = numbers.iter().sum: ();

但几乎可以肯定是打算写:

let sum = numbers.iter().sum();

编译器看到了您不小心包含的:,并认为您正在尝试使用类型归属。类型归属是在表达式中内联使用类型注释的语法,而不仅仅是在变量声明中。

你写的很像:

let sum: () = numbers.iter().sum;

如果您要在每晚的 rustc 构建中启用类型归属,错误将会改变,因为现在编译器会告诉您 sum 是一个函数,并且肯定 具有类型 () .

【讨论】:

    【解决方案3】:

    这个怎么样:

    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

    与迄今为止发布的答案相比,它似乎具有以下优势:

    1. 更简单的泛型类型定义。
    2. 不依赖外部num crate。
    3. 不需要像FromPrimitiveZero 这样难以猜测的特征。
    4. 没有手动生命周期声明。

    或者这个版本与上一个有以下不同:

    1. 可以采用数组而不是向量。
    2. 不消耗数组(或向量)。
    3. 需要手动生命周期声明。
    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),
        }
    }
    

    感谢我的朋友 Sven 的代码贡献。

    【讨论】:

    • 此解决方案不适用于isizeusize
    【解决方案4】:
    • 当编译器无法判断fn sum&lt;S&gt;(self) -&gt; S的类型S时,要么需要写let foo: Bar = baz.sum();要么写let foo = baz.sum::&lt;Bar&gt;();

    • 1234563否则,您可能需要使用参考。
    • 你可以让你的函数更通用一点,返回Option&lt;T&gt;,但如果你真的想返回Option&lt;f64&gt;,你应该使用ToPrimitive trait 将T 转换为f64。喜欢this

    【讨论】:

    • 如果我不能使用整数类型作为输入,我为什么要使用通用均值方法?
    • 在这个特定的用例中,可以使用整数数组(u8i32u64)作为输入。唯一的缺点是除法截断,即println!("{:.3}", _) 永远不会显示浮点数。
    • 当然,但不幸的是,这会扼杀平均函数的目的(质量)。
    • 你是对的,@ÖmerErden。我以正确的方式更新了答案。谢谢!
    • @Caio 感谢您的提示。我认为最好返回Option&lt;f64&gt;,因为整数数组的平均值很可能是浮点数,我不想截断答案。
    猜你喜欢
    • 2023-03-08
    • 2017-09-19
    • 2015-08-11
    • 1970-01-01
    • 1970-01-01
    • 2016-05-28
    • 2023-02-01
    • 1970-01-01
    相关资源
    最近更新 更多