IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Rust par l'exemple


précédentsommairesuivant

8. Les fonctions

Les fonctions sont déclarées à l'aide du mot-clé fn. Leurs arguments sont typés, tout comme les variables, et, si la fonction renvoie une valeur, le type renvoyé doit être spécifié à la suite d'une flèche ->.

La dernière expression se trouvant dans le corps de la fonction sera utilisée pour inférer le type de renvoi. Il est également possible d'utiliser l'instruction return pour effectuer un renvoi prématuré dans la fonction (peut être utilisé dans les boucles et les structures conditionnelles).

Réécrivons les règles de FizzBuzz en utilisant les fonctions !

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
// Contrairement à C ou C++, l'ordre de déclaration des fonctions 
// n'est pas important.
fn main() {
    // Nous pouvons appeler cette fonction ici et l'implémenter ailleurs.
    fizzbuzz_to(100);
}

// Une fonction qui renvoie une valeur booléenne.
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
    // Comportement imprévisible, renvoi prématuré.
    if rhs == 0 {
        return false;
    }

    // C'est une expression, le mot-clé `return` n'est pas nécessaire ici.
    lhs % rhs == 0
}
// Les fonctions qui n'ont pas de type de renvoi défini renvoient par défaut 
// un tuple vide `()`.
fn fizzbuzz(n: u32) -> () {
    if is_divisible_by(n, 15) {
        println!("fizzbuzz");
    } else if is_divisible_by(n, 3) {
        println!("fizz");
    } else if is_divisible_by(n, 5) {
        println!("buzz");
    } else {
        println!("{}", n);
    }
}

// Quand une fonction ne possède pas de type de renvoi, le tuple vide `()` 
// peut être omis.
fn fizzbuzz_to(n: u32) /* type de renvoi omis */ {
    for n in 1..n + 1 {
        fizzbuzz(n);
    }
}

8-1. Les méthodes

Les méthodes sont des fonctions rattachées à des structures et objets. Ces méthodes ont un accès aux données de l'objet ainsi qu'à ses autres méthodes par le biais du mot-clé self. Les méthodes sont déclarées dans un bloc impl.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
struct Point {
    x: f64,
    y: f64,
}

// Toutes les méthodes de la structure `Point` sont implémentées ici.
impl Point {
    // Ceci est une méthode statique.
    // Les méthodes statiques ne sont pas dépendantes des instances.
    // Il est courant d'utiliser les méthodes statiques comme constructeurs.
    fn origin() -> Point {
        Point { x: 0.0, y: 0.0 }
    }

    // Une autre méthode statique possédant
    // deux paramètres.
    fn new(x: f64, y: f64) -> Point {
        Point { x: x, y: y }
    }
}

struct Rectangle {
    p1: Point,
    p2: Point,
}

impl Rectangle {
    // Ceci est une méthode dépendante d'une instance (méthode d'instance).
    // `&self` est le sucre syntaxique de `self: &Self` où `Self` est le 
    // type de l'objet qui appelle les méthodes. En l'occurrence 
    // `Self` = `Rectangle`.
    fn area(&self) -> f64 {
        // `self` donne accès aux champs de la structure via la notation pointée.
        let Point { x: x1, y: y1 } = self.p1;
        let Point { x: x2, y: y2 } = self.p2;

        // `abs` est une méthode renvoyant un réel de type f64 qui représente 
        // la valeur absolue du primitif.
        ((x1 - x2) * (y1 - y2)).abs()
    }

    fn perimeter(&self) -> f64 {
        let Point { x: x1, y: y1 } = self.p1;
        let Point { x: x2, y: y2 } = self.p2;

        2.0 * ((x1 - x2).abs() + (y1 - y2).abs())
    }

    // Cette méthode a besoin d'opérer sur une référence mutable 
    // de l'instance courante `&mut self`.
    // La syntaxe non-raccourcie est `self: &mut Self`.
    fn translate(&mut self, x: f64, y: f64) {
        self.p1.x += x;
        self.p2.x += x;

        self.p1.y += y;
        self.p2.y += y;
    }
}

// `Pair` possède deux entiers alloués dans le tas.
struct Pair(Box<i32>, Box<i32>);

impl Pair {
    // Cette méthode "consomme" les ressources de l'instance courante.
    // `self` est un sucre syntaxique de `self: Self`.
    fn destroy(self) {
        // Déstructure `self` (i.e. récupère les champs désirés).
        let Pair(first, second) = self;

        println!("Destroying Pair({}, {})", first, second);

        // La mémoire occupée par `first` et `second` sera libérée
        // une fois que l'exécution de la méthode prendra fin.
    }
}

fn main() {
    let rectangle = Rectangle {
        // Les méthodes statiques sont appelées par le biais 
        // d'une paire de deux points `::`.
        p1: Point::origin(),
        p2: Point::new(3.0, 4.0),
    };

    // Vous devez, en revanche, utiliser la notation pointée pour appeler 
    // les méthodes d'instance. 
    // Notez que le premier argument passé (implicitement) est `&self`
    // (i.e. `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)`).
    println!("Rectangle perimeter: {}", rectangle.perimeter());
    println!("Rectangle area: {}", rectangle.area());

    let mut square = Rectangle {
        p1: Point::origin(),
        p2: Point::new(1.0, 1.0),
    };

    // Erreur! `rectangle` est immuable alors que cette méthode 
    // nécessite une référence mutable de l'objet.
    // rectangle.translate(1.0, 0.0);
    // TODO ^ Essayez de décommenter cette ligne.

    // C'est bon! Les objets mutables peuvent appeler les méthodes
    // "mutables".
    square.translate(1.0, 1.0);

    let pair = Pair(Box::new(1), Box::new(2));

    pair.destroy();

    // Erreur! L'appel de la méthode `destroy` a consommé
    // l'instance `pair`.
    // pair.destroy();
    // TODO ^ Essayez de décommenter cette ligne.
}

Définition : valeur absolue

8-2. Les closures

Les closures en Rust, également appelées « lambdas », sont des fonctions qui peuvent capturer l'environnement qui les entoure. Par exemple, voici une closure qui capture la variable x :

 
Sélectionnez
|val| val + x

La syntaxe ainsi que les capacités des closures les rendent adéquates aux déclarations et utilisations à la volée. Appeler une closure se fait de la même manière qu'une fonction classique. En revanche, les types reçus en entrée (i.e. les types des paramètres passés) et le type de renvoi peuvent être inférés et les identificateurs des paramètres doivent être spécifiés.

D'autres caractéristiques spécifiques aux closures :

  • L'utilisation du couple || plutôt que de () pour entourer les paramètres ;
  • La délimitation {} du corps de la closure optionnelle pour une seule expression (sinon obligatoire) ;
  • La capacité à capturer des variables appartenant au contexte dans lequel la closure est imbriquée.
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
fn main() {
    // Incrémentation avec les closures et fonctions.
    fn  function            (i: i32) -> i32 { i + 1 }

    // Les closures sont anonymes, ici nous assignons leurs références.
    // Les closures sont typées de la même manière qu'une fonction classique
    // mais le typage est optionnel. Chaque version (raccourcie et non-raccourcie)
    // est assignée à un identificateur approprié.
    let closure_annotated = |i: i32| -> i32 { i + 1 };
    let closure_inferred  = |i     |          i + 1  ;

    let i = 1;
    // Appelle la fonction et les closures.
    println!("function: {}", function(i));
    println!("closure_annotated: {}", closure_annotated(i));
    println!("closure_inferred: {}", closure_inferred(i));

    // Une closure qui ne prend aucun argument et renvoie un 
    // entier de type `i32`.
    // Le type de renvoi est inféré.
    let one = || 1;
    println!("closure returning one: {}", one());

}

8-2-1. Capture

Les closures sont naturellement flexibles et feront leur possible pour fonctionner sans typage explicite. Ceci permet à la capture de s'adapter au contexte : parfois en prenant possession des ressources, parfois seulement en les empruntant. Les closures peuvent capturer les variables :

  • Par référence : &T ;
  • Par référence mutable : &mut T ;
  • Par valeur T.

Par défaut, elles privilégient la capture par référence s'il n'est pas nécessaire de prendre possession des ressources.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
fn main() {
    use std::mem;

    let color = "green";

    // Une closure destinée à afficher la variable `color` qui emprunte 
    // (`&`) `color` et stocke l'emprunt ainsi que la closure 
    // dans la variable `print`. Elle (`color`) restera "empruntée" 
    // jusqu'à ce que l'exécution de `print` prend fin. 
    // La macro println! ne fait qu'emprunter les ressources, cela 
    // n'impose pas de contraintes supplémentaires pour la closure.
    let print = || println!("`color`: {}", color);

    // On appelle la closure en empruntant `color`.
    print();
    print();

    let mut count = 0;

    // Une closure qui incrémente la variable `count`. Cette dernière
    // pourrait être exploitée par référence `&mut count` ou valeur
    // `count`, mais puisque `&mut count` est moins restrictif la capture 
    // par référence mutable sera choisie.
    //
    // La variable `inc` est annotée comme `mut` car une référence mutable 
    // `&mut` est stockée à l'intérieur. 
    // Appeler la closure(du moins, dans ce contexte) modifie 
    // son propre état et donc requiert un `mut`.
    
    let mut inc = || {
        count += 1;
        println!("`count`: {}", count);
    };

    // Appelle la closure.
    inc();
    inc();

    // let reborrow = &mut count;
    // ^ TODO: Essayez de décommenter cette ligne.

    // Un entier non-copiable.
    let movable = Box::new(3);

    // `mem::drop` prend possession de ses paramètres. 
    // Un type pouvant être copié devrait être copié dans la closure, 
    // laissant la ressource originale intacte. Un type qui ne peut pas 
    // être copié doit être déplacé et donc `movable` appartiendra à la closure.
    let consume = || {
        println!("`movable`: {:?}", movable);
        mem::drop(movable);
    };

    // `consume` prend possession de la variable et ne peut donc être appelée qu'une seule fois.
    consume();
    // consume();
    // ^ TODO: Essayez de décommenter ce second appel.
}

Voir aussi

Box et std::mem::drop.

8-2-2. Les closures passées en paramètres

Alors que Rust se charge de choisir, pour les closures, la manière de capturer les variables sans forcer le typage, lorsque c'est possible, cette ambiguïté n'est pas permise au sein des fonctions. Lorsqu'une closure est passée en paramètre à une fonction, le type de ses paramètres ainsi que celui de sa valeur de retour doivent être précisés en utilisant des traits. Dans l'ordre du plus restrictif au plus « laxiste » :

  1. Fn : La closure capture par référence (&T) ;
  2. FnMut : La closure capture par référence mutable (&mut T) ;
  3. FnOnce : La closure capture par valeur (T).

En se fiant au contexte, le compilateur va capturer les variables en privilégiant le « régime » le moins restrictif possible.

Par exemple, prenez un paramètre typé avec le trait FnOnce. Cela signifie que la closure peut capturer ses variables par référence &T, référence mutable &mut T, ou valeur T mais le compilateur reste encore le seul juge quant au régime à adopter, en fonction du contexte.

C'est pourquoi si un transfert (move) est possible alors n'importe quel type d'emprunts devrait être possible, notez que l'inverse n'est pas vrai. Si le paramètre est typé Fn alors les captures par référence mutable &mut T ou par valeur T ne sont pas permises.

Dans l'exemple suivant, essayez de modifier le type de capture (i.e. Fn, FnMut et FnOnce) pour voir ce qu'il se passe :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
// Une fonction qui prend une closure en paramètre et appelle cette dernière.
fn apply<F>(f: F) where
    // La closure ne prend rien et ne renvoie rien.
    F: FnOnce() {
    // ^ TODO: Essayez de remplacer ce trait par `Fn` ou `FnMut`.

    f();
}

// Une fonction qui prend une closure en paramètre et renvoie un entier
// de type `i32`.
fn apply_to_3<F>(f: F) -> i32 where
    // La closure prend en paramètre un `i32` et renvoie 
    // un `i32`.
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // Un type qui ne peut pas être copié.
    // `to_owned` crée une ressource dont 
    // l'assignation `farewell` sera responsable, à partir d'une ressource empruntée.
    let mut farewell = "goodbye".to_owned();

    // Capture deux variables: `greeting` par référence et 
    // `farewell` par valeur.
    let diary = || {
        // `greeting` est capturé par référence: requiert `Fn`.
        println!("I said {}.", greeting);

        // Le fait de modifier `farewell` rend obligatoire 
        // la capture par référence mutable, le compilateur choisira 
        // donc `FnMut`.
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");

        // Appeler manuellement la fonction `drop` nécessite 
        // désormais de capturer par valeur `farewell`, le compilateur 
        // choisira alors `FnOnce`.
        mem::drop(farewell);
    };

    // On appelle la fonction qui prend en paramètre la closure.
    apply(diary);

    // `double` satisfait les conditions du trait soumis à `apply_to_3`.
    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}

Voir aussi

La fonction std::mem::drop et les traits Fn, FnMut et FnOnce.

8-2-3. Les types anonymes

Les closures capturent succinctement les variables se trouvant dans les contextes qui les ont engendré. Cela a-t-il des conséquences ? Certainement. Nous remarquons qu'une fonction prête à recevoir une closure doit posséder un paramètre générique pour définir le « régime » de capture que la closure adoptera :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
// `F` doit être générique.
fn apply<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

Quand une closure est définie, le compilateur crée implicitement une structure anonyme pour stocker les variables capturées par la closure. Cette structure implémentera également l'un des traits rencontrés précédemment : Fn, FnMut ou FnOnce. Ce type anonyme est assigné à la variable stockée jusqu'à ce que la closure soit appelée.

Puisque le type créé implicitement est inconnu, son utilisation dans le corps d'une fonction nécessitera un paramètre générique. Cependant, un paramètre <T> dont le trait n'est pas précisé pourrait toujours être ambiguë et rejeté par le compilateur. Il est donc nécessaire de préciser quels services (i.e. Fn, FnMut ou FnOnce) il implémentera.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
// `F` doit implémenter `Fn` pour une closure qui ne prend aucun 
// argument et ne renvoie rien - exactement ce qui est nécessaire 
// pour `print`.
fn apply<F>(f: F) where
    F: Fn() {
    f();
}

fn main() {
    let x = 7;

    // Capture la variable `x` dans une structure anonyme 
    // et implémente `Fn` pour cette dernière. On stocke dans `print`.
    let print = || println!("{}", x);

    apply(print);
}

Voir aussi

Une analyse complète des closures, Fn, FnMut et FnOnce.

8-2-4. Fonctions passées en paramètres

Les closures peuvent être soumises en entrée aux fonctions, mais vous pourriez vous demander si nous pouvons faire de même avec d'autres fonctions. C'est le cas ! Si vous déclarez une fonction qui prend une closure en paramètre alors n'importe quelle fonction implémentant les traits requis peut être passée en paramètre.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
// On déclare une fonction qui prend l'argument générique `F`
// délimité par le trait `Fn` et appelle la fonction (ou closure).
fn call_me<F: Fn()>(f: F) {
    f();
}

// On déclare une fonction qui satisfait la délimitation (hériter de `Fn`).
fn function() {
    println!("I'm a function!");
}

fn main() {
    // On déclare une closure qui satisfait la délimitation (hériter de `Fn`).
    let closure = || println!("I'm a closure!");

    call_me(closure);
    call_me(function);
}

Voir aussi

Fn, FnMut et FnOnce.

8-2-5. Renvoyer une closure

Les closures peuvent être passées en paramètre à une fonction, donc les renvoyer devrait être possible. Cependant, renvoyer un « type » de closure est problématique car, actuellement, Rust ne supporte le renvoi que de types concrets (i.e. non-génériques). Le type anonyme d'une closure est, par définition, inconnu donc le renvoi d'une closure ne peut être fait qu'en rendant son type concret.

Les traits destinés à valider le renvoi d'une closure sont quelque peu différents :

  • Fn : pas de changements pour ce trait ;
  • FnMut : pas de changements pour ce trait ;
  • FnOnce : Différentes choses entrent en jeu ici, donc le type FnBox doit être utilisé à la place de FnOnce. Notez toutefois que FnBox est taggé instable et que des modifications pourraient être apportées dans le futur.

En dehors de cela, le mot-clé move doit être utilisé, indiquant que toutes les captures se feront par valeur pour la closure courante. Il est nécessaire d'utiliser move car aucune capture par référence ne pourrait être libérée aussitôt la fonction terminée, laissant des références invalides dans la closure.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
fn create_fn() -> Box<Fn()> {
    let text = "Fn".to_owned();

    Box::new(move || println!("This is a: {}", text))
}

fn create_fnmut() -> Box<FnMut()> {
    let text = "FnMut".to_owned();

    Box::new(move || println!("This is a: {}", text))
}

fn main() {
    let fn_plain = create_fn();
    let mut fn_mut = create_fnmut();

    fn_plain();
    fn_mut();
}

Voir aussi

Box, Fn, FnMut et la généricité.

8-2-6. Exemples de la bibliothèque standard

Cette section contient quelques exemples d'utilisation de closures avec des outils fournis par la bibliothèque standard.

8-2-6-1. Iterator::any

Iterator::any est une fonction qui, lorsqu'un itérateur est passé en paramètre, renvoie true si au moins un élément satisfait le prédicat, autrement false. Voici sa signature :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
pub trait Iterator {
    // Le type sur lequel on va itérer.
    type Item;

    // `any` prend en paramètre une référence mutable `&mut self` de
    // l'instance courante qui sera empruntée et modifiée, mais pas consommée
    // (possédée).
    fn any<F>(&mut self, f: F) -> bool
    where
        F: FnMut(Self::Item) -> bool,
    {
        true
    }
}
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];

    // `iter()`, pour les vecteurs, fournit la référence de chaque 
    // élément `&i32`. 
    println!("2 in vec1: {}", vec1.iter()     .any(|&x| x == 2));
    // `into_iter()`, pour les vecteurs, fournit la valeur de chaque élément `i32`.
    // L'itérateur est consommé.
    println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));
    let array1 = [1, 2, 3];
    let array2 = [4, 5, 6];

    // `iter()` fournit la référence de chaque élément du tableau `&i32`.
    println!("2 in array1: {}", array1.iter()     .any(|&x| x == 2));
    // `into_iter()` fournit, exceptionnellement, la référence de chaque élément
    // du tableau `&i32` (le type i32 implémente les traits requis).
    println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2));
}

Voir aussi

std::iter::Iterator::any.

8-2-6-2. Iterator::find

Iterator::find est une fonction qui renvoie le premier élément correspondant au prédicat.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
pub trait Iterator {
    // Le type sur lequel on va itérer.
    type Item;

    // `find` prend en paramètre une référence mutable de l'instance courante
    // `&mut self`. Elle sera donc empruntée et modifiée, mais pas consommée.
    fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
    where
        P: FnMut(&Self::Item) -> bool,
    {
        None
    }
}
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
fn main() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];

    // `iter()` fournit la référence de chaque élément `&i32` du vecteur.
    let mut iter = vec1.iter();
    // `into_iter()` fournit la valeur de chaque élément du vecteur.
    let mut into_iter = vec2.into_iter();

    // Référence fournie par `iter`: `&&i32`. On déstructure la référence
    // de la référence pour obtenir l'entier `i32`.
    println!("Find 2 in vec1: {:?}", iter     .find(|&&x| x == 2));
    // Référence fournie par `into_iter`: `&i32`. On déstructure la référence 
    // pour obtenir l'entier `i32`.
    println!("Find 2 in vec2: {:?}", into_iter.find(| &x| x == 2));

    let array1 = [1, 2, 3];
    let array2 = [4, 5, 6];

    // `iter()` fournit la référence de chaque élément du tableau `&i32`.
    println!("Find 2 in array1: {:?}", array1.iter()     .find(|&&x| x == 2));
    // `into_iter()` fournit, exceptionnellement, la référence de chaque élément
    // du tableau `&i32` (le type i32 implémente les traits requis).
    println!("Find 2 in array2: {:?}", array2.into_iter().find(|&&x| x == 2));
}

Voir aussi

std::iter::Iterator::find.

8-3. Les fonctions d'ordre supérieur

Rust supporte les fonctions d'ordre supérieur (HOF). Ces fonctions prennent en paramètre une ou plusieurs fonctions et renvoie une autre fonction. Ce sont les HOF ainsi que les « itérateurs laxistes » qui donnent cet aspect fonctionnel à Rust.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
fn is_odd(n: u32) -> bool {
    n % 2 == 1
}

fn main() {
    println!("Find the sum of all the squared odd numbers under 1000");
    let upper = 1000;

    // Approche impérative.
    // On déclare un accumulateur.
    let mut acc = 0; // 0, 1, 2, ... ∞
    for n in 0.. {
        // Le carré du nombre.
        let n_squared = n * n;

        if n_squared >= upper {
            // On sort de la boucle si le carré de `n_squared` 
            // dépasse la limite imposée par `upper`.
            break;
        } else if is_odd(n_squared) {
            // On accumule le carré de `n_squared` si c'est impair.
            acc += n_squared;
        }
    }
    println!("imperative style: {}", acc);

    // Approche fonctionnelle.
    let sum_of_squared_odd_numbers: u32 =
        (0..).map(|n| n * n)             // On calcule le carré de chaque nombre.
             .take_while(|&n| n < upper) // On vérifie que le carré se trouve toujours sous la limite de `upper`.
             .filter(|&n| is_odd(n))     // On récupère le nombre si il est impair.
             .fold(0, |sum, i| sum + i); // On accumule le carré du nombre impair.
    println!("functional style: {}", sum_of_squared_odd_numbers);
}

L'énumération Option et le trait Iterator implémentent leur lot d'HOF.


précédentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Rust Core Team et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.