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 Réponses :
Lorsque le compilateur ne peut pas déterminer le type S
de fn sum
, vous devez soit écrire < code> let foo: Bar = baz.sum (); ou (self) -> S 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 .
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.
Vous effectuez 2 opérations différentes dans votre fonction générique:
Sum
à votre paramètre de type générique. 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) } } }
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.
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 () .
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:
num
. FromPrimitive
et Zero
. Ou cette version qui présente les différences suivantes par rapport à celle ci-dessus:
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.
Cette solution ne fonctionne pas pour les valeurs isize
ou usize
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 frappeEssayez de déduire vous-même le type de
longueur
etsomme
. Et vous comprendrez pourquoi le compilateur ne peut pas faire cela. Spoiler: il peut y avoir de nombreux types qui implémententnum :: FromPrimitive
. Et il peut y avoir de nombreux types qui implémententstd::iter::Sum<&T>
. Tout comme il peut y avoir de nombreux types qui implémententstd :: ops :: Div <_, Output = f64>
(_ ici parce que le type delength
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