web-dev-qa-db-fra.com

Comment écrire une fonction Rust qui prend un itérateur?

Je voudrais écrire une fonction qui accepte un itérateur et renvoie les résultats de certaines opérations dessus. Plus précisément, j'essaie d'itérer sur les valeurs d'un HashMap:

use std::collections::HashMap;

fn find_min<'a>(vals: Iterator<Item=&'a u32>) -> Option<&'a u32> {
    vals.min()
}

fn main() {
    let mut map = HashMap::new();
    map.insert("zero", 0u32);
    map.insert("one", 1u32);
    println!("Min value {:?}", find_min(map.values()));
}

Mais hélas:

error: the `min` method cannot be invoked on a trait object
 --> src/main.rs:4:10
  |
4 |     vals.min()
  |          ^^^

error[E0277]: the trait bound `std::iter::Iterator<Item=&'a u32> + 'static: std::marker::Sized` is not satisfied
 --> src/main.rs:3:17
  |
3 | fn find_min<'a>(vals: Iterator<Item = &'a u32>) -> Option<&'a u32> {
  |                 ^^^^ `std::iter::Iterator<Item=&'a u32> + 'static` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `std::iter::Iterator<Item=&'a u32> + 'static`
  = note: all local variables must have a statically known size

error[E0308]: mismatched types
  --> src/main.rs:11:41
   |
11 |     println!("Min value {:?}", find_min(map.values()));
   |                                         ^^^^^^^^^^^^ expected trait std::iter::Iterator, found struct `std::collections::hash_map::Values`
   |
   = note: expected type `std::iter::Iterator<Item=&u32> + 'static`
              found type `std::collections::hash_map::Values<'_, &str, u32>`

J'obtiens la même erreur si j'essaye de passer par référence; si j'utilise un Box, j'obtiens des erreurs à vie.

47
Doctor J

Vous souhaitez utiliser des génériques ici:

fn find_min<'a, I>(vals: I) -> Option<&'a u32>
where
    I: Iterator<Item = &'a u32>,
{
    vals.min()
}

Les traits peuvent être utilisés de deux manières: comme limites sur les paramètres de type et comme objets de trait. Le livre Le Rust langage de programmation a un chapitre sur traits et un chapitre sur - objets trait qui expliquent ces deux cas d'utilisation.

De plus, vous voulez souvent prendre quelque chose qui implémente IntoIterator car cela peut rendre le code appelant votre fonction plus agréable:

fn find_min<'a, I>(vals: I) -> Option<&'a u32>
where
    I: IntoIterator<Item = &'a u32>,
{
    vals.into_iter().min()
}
41
Francis Gagné

Ce comportement n'est pas intuitif de ceux avec un arrière-plan Python plutôt que, disons, un arrière-plan C++, alors permettez-moi de clarifier un peu.

Dans Rust, les valeurs sont conceptuellement stockées dans le nom qui les lie. Ainsi, si vous écrivez

let mut x = Foo { t: 10 };
let mut y = x;
x.t = 999;

y.t sera toujours 10.

Alors quand tu écris

let x: Iterator<Item=&'a u32>;

(ou la même chose dans la liste des paramètres de fonction), Rust doit allouer suffisamment d'espace pour any valeur de type Iterator<Item=&'a u32>. Même si c'était possible, ce ne serait pas efficace.

Donc ce que Rust fait à la place est de vous offrir la possibilité de

  • Mettez la valeur sur le tas, par exemple. avec Box, qui donne une sémantique de style Python. Ensuite, vous pouvez prendre génériquement avec &mut Iterator<Item=&'a u32>.

  • Spécialisez chaque invocation de fonction pour chaque type possible pour satisfaire la limite. C'est plus flexible, car une référence de trait est une spécialisation possible, et donne au compilateur plus de possibilités de spécialisation, mais signifie que vous ne pouvez pas avoir dynamic dispatch (où le type peut varier en fonction des paramètres d'exécution) .

12
Veedrac

Le plus simple est d'utiliser un implicite trait , impl Iterator:

use std::collections::HashMap;

fn find_min<'a>(vals: impl Iterator<Item = &'a u32>) -> Option<&'a u32> {
    vals.min()
}

fn main() {
    let mut map = HashMap::new();
    map.insert("zero", 0u32);
    map.insert("one", 1u32);
    println!("Min value {:?}", find_min(map.values()));
}

aire de jeux

0
Ben