1
votes

Syntaxe des génériques Rust pour la fonction moyenne

J'essaie d'écrire une fonction qui prend une tranche de nombres et calcule la moyenne.

J'ai essayé d'utiliser les idées de Implémentation de la fonction moyenne pour les types génériques mais obtention d'une erreur.

Mon code est:

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

L'erreur est:

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

Existe-t-il un moyen d'écrire une fonction moyenne générique sans utiliser de fonctionnalités expérimentales?


4 commentaires

La question que vous avez liée ne suggère pas d'écrire sum: () . Ce n'est pas une syntaxe valide dans Rust aujourd'hui et je ne peux que supposer que c'était une faute de frappe


Essayez de déduire vous-même le type de longueur et somme . Et vous comprendrez pourquoi le compilateur ne peut pas faire cela. Spoiler: il peut y avoir de nombreux types qui implémentent num :: FromPrimitive . Et il peut y avoir de nombreux types qui implémentent std::iter::Sum<&T> . Tout comme il peut y avoir de nombreux types qui implémentent std :: ops :: Div <_, Output = f64> (_ ici parce que le type de length ne peut pas être déduit ).


Et pour que l'inférence fonctionne, vous devez vous assurer qu'il n'y a pas d'ambiguïtés.


Très lié: stackoverflow.com/q/41017140/1233251


4 Réponses :


0
votes
  • Lorsque le compilateur ne peut pas déterminer le type S de fn sum (self) -> S , vous devez soit écrire < code> let foo: Bar = baz.sum (); ou let foo = baz.sum :: ();

  • Si vous êtes sûr que T sera toujours un type de primitive numérique, vous devez collecter un type appartenant à sum () avec laissez sum: T = numbers.iter (). cloned (). sum (); et ajoutez le core :: iter :: Sum lié ​​à T . Sinon, vous souhaiterez peut-être travailler avec des références.

  • Vous pouvez rendre votre fonction un peu plus générique en retournant Option mais si vous voulez vraiment retourner Option , vous devez lancer T à f64 en utilisant le trait ToPrimitive . Comme ceci .


5 commentaires

si je ne peux pas utiliser de types entiers comme entrée, pourquoi devrais-je créer une méthode moyenne générique?


Dans ce cas d'utilisation particulier, il est possible d'utiliser un tableau d'entiers ( u8 , i32 ou u64 ) comme entrée. Le seul inconvénient serait la troncature de division, c'est-à-dire que println! ("{:. 3}", _) n'affichera jamais les nombres flottants.


Bien sûr, mais malheureusement cela tue le but (qualité) de la fonction moyenne.


Vous avez raison, @ ÖmerErden. J'ai mis à jour la réponse correctement. Merci!


@Caio Merci pour les conseils. J'ai pensé qu'il valait mieux renvoyer Option car la moyenne d'un tableau d'entiers est assez probablement un nombre à virgule flottante et je ne voulais pas tronquer la réponse.



7
votes

Vous effectuez 2 opérations différentes dans votre fonction générique:

  • Addition de toutes les valeurs de la tranche: vous devez indiquer que vos éléments peuvent être sommés en ajoutant une limite Sum à votre paramètre de type générique.
  • Opération de division avec 2 éléments: votre type générique doit être converti en f64 ou en tout type flottant que vous souhaitez limiter. Puisque vous utilisez num crate, j'ai ajouté ToPrimitive comme limite qui indique que votre type générique peut être converti en un type primitif.

Voici l'implémentation:

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

Aire de jeux


2 commentaires

Merci infiniment. Venant d'années de Python, je suis étonné de voir à quel point les traits peuvent devenir si rapidement compliqués. Je n'aurais jamais pensé que j'avais besoin de traits comme Zero et ToPrimitive d'une bibliothèque tierce pour faire quelque chose d'aussi simple (en apparence) que de prendre la moyenne de certains nombres.


@blokeley comme prenant la moyenne de certains nombres - c'est le "problème": vous ne prenez pas la moyenne de "certains nombres". Vous prenez la moyenne de tout type générique qui peut exprimer un ensemble de propriétés. En Python, vous pouvez transmettre une collection de chaînes à la fonction équivalente et obtenir une erreur d'exécution. Le code Rust équivalent garantit que tout ce que vous faites est valide lorsque le code est compilé. Vous pouvez créer votre propre trait propre et l’implémenter uniquement pour les nombres afin de réduire la complexité apparente de cette fonction.



3
votes

Les autres réponses vous aideront probablement à résoudre votre vrai problème d'écriture générique de cette fonction.


La véritable erreur que vous avez posée est juste une erreur de syntaxe. Vous avez écrit ceci:

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

Mais presque certainement destiné à écrire:

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

Le compilateur a vu le : que vous avez accidentellement inclus et pense que vous essayez d'utiliser l'attribution de type. L'attribution de type est une syntaxe permettant d'utiliser des annotations de type en ligne dans une expression, au lieu de simplement dans des déclarations de variables.

Ce que vous avez écrit est très similaire à:

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

Si vous deviez activer l'attribution de type dans une build nightly rustc, l'erreur changerait car maintenant le compilateur vous dira que sum est une fonction et n'a certainement pas de type () .


0 commentaires

1
votes

Que diriez-vous de ceci:

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

Même code dans le Rust Playground

Il semble avoir les avantages suivants par rapport aux réponses publiées jusqu'à présent:

  1. Définition de type générique beaucoup plus simple.
  2. Pas de dépendance à l'égard de la caisse externe num .
  3. Pas besoin de traits difficiles à deviner comme FromPrimitive et Zero .
  4. Aucune déclaration manuelle de durée de vie.

Ou cette version qui présente les différences suivantes par rapport à celle ci-dessus:

  1. Peut prendre des tableaux plutôt que des vecteurs.
  2. Ne consomme pas le tableau (ou le vecteur).
  3. Nécessite des déclarations de durée de vie manuelles.
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)
    }
}

Merci à mon ami Sven pour la contribution au code.


1 commentaires

Cette solution ne fonctionne pas pour les valeurs isize ou usize