Partage de technologie

Apprentissage du langage de programmation Rust - fonctionnalités du langage fonctionnel : itérateurs et fermetures

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Rouiller Une fermeture est une fonction anonyme qui peut être enregistrée dans une variable ou passée en argument à d'autres fonctions. Vous pouvez créer une fermeture à un endroit, puis effectuer des opérations de fermeture dans un contexte différent. Contrairement aux fonctions, les fermetures permettent de capturer des valeurs dans le cadre dans lequel elles sont définies.

Un itérateur est responsable de la logique de parcours de chaque élément de la séquence et de déterminer quand la séquence se termine. Lorsque nous utilisons des itérateurs, nous n'avons pas besoin de réimplémenter cette logique.

1. Clôture

Fermeture, une structure semblable à une fonction qui peut être stockée dans une variable.

1.1 Les fermetures capturent leur environnement

Dans Rust, les fermetures sont des fonctions anonymes qui capturent des variables de l'environnement externe. Le comportement de capture d'une fermeture dépend des types de variables et de la manière dont la fermeture utilise ces variables.

Capture par valeur (Par valeur)

Lorsqu'une fermeture capture une variable par valeur, elle s'approprie la variable. Cela signifie qu'une fois la fermeture créée, la variable d'origine ne peut plus être utilisée.

fn main() {
    let text = "Hello".to_string();
    // 使用 move 来显式地表示闭包将获取 text 的所有权
    let closure = move || println!("{}", text);
    // 这里 text 不能被使用,因为其所有权已经被闭包获取
    // println!("{}", text); // 这将导致编译错误
    closure(); // 打印 "Hello"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Capture par référence (Par référence)

Lorsqu'une fermeture capture une variable par référence, elle emprunte cette variable. Cela signifie que la variable d'origine est toujours disponible, mais que la fermeture ne peut que l'emprunter, pas en prendre possession.

fn main() {
    let text = "Hello";
    // 闭包通过引用捕获 text
    let closure = || println!("{}", text);
    // text 仍然可用,因为它没有被移动
    println!("{}", text); // 打印 "Hello"
    closure(); // 再次打印 "Hello"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Capture mutable

Une fermeture peut capturer une référence mutable, lui permettant de modifier la valeur de la variable d'origine.

fn main() {
    let mut count = 0;
    // 闭包通过可变引用捕获 count
    let mut closure = || {
        count += 1; // 修改 count 的值
        println!("Count: {}", count);
    };
    closure(); // 打印 "Count: 1"
    closure(); // 打印 "Count: 2"
    // count 的值现在是 2
    println!("Final count: {}", count);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Si la fermeture est retirée mut Modification, la compilation échoue.

fn main() {
    let mut count = 0;
    // 闭包通过可变引用捕获 count
    let closure = || {
        count += 1; // 修改 count 的值
        println!("Count: {}", count);
    };
    closure(); // 打印 "Count: 1"
    closure(); // 打印 "Count: 2"
    // count 的值现在是 2
    println!("Final count: {}", count);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Message d'erreur : En raison de l'emprunt d'une variable count,transfert closure Une liaison mutable est requise.

   Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow `closure` as mutable, as it is not declared as mutable
 --> src/main.rs:4:9
  |
4 |     let closure = || {
  |         ^^^^^^^ not mutable
5 |         count += 1; // 修改 count 的值
  |         ----- calling `closure` requires mutable binding due to mutable borrow of `count`
...
8 |     closure(); // 打印 "Count: 1"
  |     ------- cannot borrow as mutable
9 |     closure(); // 打印 "Count: 2"
  |     ------- cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
4 |     let mut closure = || {
  |         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground` (bin "playground") due to 1 previous error
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

clôture comme argument

Les fermetures peuvent être utilisées commePassage de paramètresPour d'autres fonctions, capturez les variables dans l'environnement.

fn main() {
    // 创建一个整数变量
    let number = 10;

    // 创建一个闭包,它接受一个 i32 类型的参数并返回其平方
    // 这里使用 || 表示这是一个闭包
    let square = || number * number;

    // 定义一个函数,它接受一个闭包作为参数并调用它
    // 闭包作为参数需要指定其类型,这里使用 || -> i32 表示闭包没有参数并返回 i32 类型的值
    fn call_closure<F>(f: F)
    where
        F: Fn() -> i32, // 使用 trait bound 指定闭包的签名
    {
        // 调用闭包并打印结果
        let result = f();
        println!("The result is: {}", result);
    }

    // 调用 `call_closure` 函数,并将闭包 `square` 作为参数传递
    // 由于闭包 `square` 没有参数,我们可以直接传递
    call_closure(square);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Dans cet exemple :

  1. Nous définissons une fermeture square, qui capture par référence main en fonctionnumber variable et calculer son carré.
  2. Nous définissons un fichier appelé call_closure fonction, qui accepte unFn() -> i32 Les fermetures liées aux traits prennent des paramètres, ce qui signifie que la fermeture ne prend aucun paramètre et renvoie uni32 type de valeur.
  3. Nous appelons call_closure fonction et volontésquare Les fermetures sont passées en arguments.parce quesquare Les fermetures n'ont pas de paramètres, elles peuvent être transmises directement en tant que paramètres àcall_closure
  4. call_closure La fonction appelle la fermeture et imprime le résultat.

1.2 Inférence et annotations du type de fermeture

Il existe de nombreuses autres différences entre les fonctions et les fermetures.Les fermetures ne nécessitent pas toujours quelque chose commefn Spécifiez les types de paramètres et les valeurs de retour comme les fonctions. Les annotations de type sont requises dans les fonctions car elles font partie de l'interface explicite exposée à l'utilisateur. Il est important de définir strictement ces interfaces pour garantir que chacun ait une compréhension cohérente des fonctions utilisées et des types de valeurs de retour. En revanche, les fermetures ne sont pas utilisées pour de telles interfaces exposées : elles sont stockées dans des variables et utilisées sans les nommer ni les exposer aux utilisateurs de la bibliothèque.

Les clôtures sont généralement de courte durée et concernent un contexte étroit plutôt que toute situation arbitraire. Dans ces contextes restreints, le compilateur peut déduire de manière fiable les types de paramètres et les valeurs de retour, de la même manière qu'il peut déduire les types de la plupart des variables (bien qu'il existe de rares cas où le compilateur nécessite des annotations de type de fermeture).

Semblables aux variables, des annotations de type peuvent être ajoutées si nous souhaitons augmenter la clarté et la clarté, avec l'inconvénient de rendre le code plus verbeux (par opposition à strictement nécessaire).

fn main() {
    let a = 100;
    let add_one = |x: i32| -> i32 { x + 1 };
    let b = add_one(a);
    println!("{}", b); 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Avec les annotations de type, la syntaxe des fermetures est plus similaire à celle des fonctions. Voici une comparaison verticale de la définition d'une fonction qui en ajoute un à son argument et de la syntaxe de fermeture qui a le même comportement. Certains espaces ont été ajoutés ici pour aligner les pièces correspondantes. Cela montre à quel point la syntaxe de fermeture est similaire à la syntaxe de fonction, à l'exception de l'utilisation de tubes et de certaines syntaxes facultatives :

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;
  • 1
  • 2
  • 3
  • 4

L’exemple ci-dessus peut donc être simplifié comme suit :

fn main() {
    let a = 100;
    let add_one = |x| x + 1;
    let b = add_one(a);
    println!("{}", b); 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Le compilateur déduit un type concret pour chaque paramètre et valeur de retour dans la définition de fermeture.

Regardons un autre exemple :

fn main() {
    let a = 100i32;
    let a1 = 100f32;
    let closure = |x| x;
    let b = closure(a);
    let b1 = closure(a1);
    println!("{}", b); 
    println!("{}", b1); 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Notez que cette définition de fermeture n'ajoute aucune annotation de type, nous pouvons donc appeler cette fermeture avec n'importe quel type. Mais si vous essayez d'appeler la fermeture deux fois, la première fois en utilisant i32 et la deuxième fois en utilisant f32, une erreur sera signalée :Attendu, puisque la fermeture était précédemment appelée avec un argument de type "i32".

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:6:22
  |
6 |     let b1 = closure(a1);
  |              ------- ^^ expected `i32`, found `f32`
  |              |
  |              arguments to this function are incorrect
  |
note: expected because the closure was earlier called with an argument of type `i32`
 --> src/main.rs:5:21
  |
5 |     let b = closure(a);
  |             ------- ^ expected because this argument is of type `i32`
  |             |
  |             in this closure call
note: closure parameter defined here
 --> src/main.rs:4:20
  |
4 |     let closure = |x| x;
  |                    ^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

1.3 Déplacer les valeurs capturées hors des fermetures et des traits Fn

Une fois qu'une fermeture capture une référence à, ou la propriété d'une valeur dans l'environnement dans lequel elle est définie (affectant ainsi ce qui, le cas échéant, est déplacé dans la fermeture), le code dans le corps de la fermeture définit la référence plus tard lorsque la fermeture est évalué. Ou comment la valeur est manipulée (ce qui affecte ce qui, le cas échéant, est supprimé de la fermeture). Le corps de la fermeture peut effectuer l'une des opérations suivantes : déplacer une valeur capturée hors de la fermeture, modifier la valeur capturée, ne pas déplacer ni modifier la valeur, ou ne jamais capturer la valeur de l'environnement en premier lieu.

La façon dont une fermeture capture et gère les valeurs dans l'environnement affecte les traits que la fermeture implémente. Les traits sont un moyen pour les fonctions et les structures de spécifier les types de fermetures qu'elles peuvent utiliser.Selon la façon dont le corps de fermeture gère les valeurs, la fermeture implémente automatiquement et progressivement un, deux ou troisFn trait.

  1. FnOnce S'applique aux fermetures qui peuvent être appelées une fois. Toutes les fermetures implémentent au moins ce trait car toutes les fermetures peuvent être appelées.Une fermeture qui déplace les valeurs capturées hors du corps de la fermeture met uniquement en œuvreFnOnce trait car il ne peut être appelé qu’une seule fois.
  2. FnMut Convient aux fermetures qui ne déplacent pas la valeur capturée hors du corps de la fermeture, mais cela peut modifier la valeur capturée. Ce type de fermeture peut être appelé plusieurs fois.
  3. Fn S'applique aux fermetures qui ne déplacent pas la valeur capturée hors du corps de la fermeture ni ne modifient la valeur capturée, et incluent bien sûr les fermetures qui ne capturent pas la valeur de l'environnement. De telles fermetures peuvent être appelées plusieurs fois sans modifier leur environnement, ce qui est important dans les scénarios où les fermetures sont appelées plusieurs fois simultanément.

Voici un exemple montrant comment l'utiliser dans une fonction FnOnce comme contrainte générique et assurez-vous que la fermeture n'est appelée qu'une seule fois :

fn call_once<F, T>(f: F) -> T
where
    F: FnOnce() -> T, // 约束 F 为 FnOnce trait,意味着它接受一个空参数并返回 T 类型
{
    f() // 调用闭包并返回结果
}

fn main() {
    // 创建一个闭包,它捕获了 `value` 的所有权
    let value = 42;
    let consume = move || {
        let result = value; // 移动 `value`
        println!("The value is: {}", result);
        result // 返回结果
    };

    // 调用 `call_once` 函数,传入闭包
    let result = call_once(consume);
    println!("Result of the closure: {}", result);

    // 尝试再次使用 `consume` 将会导致编译错误,因为它已经消耗了 `value`
    // call_once(consume);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

résultat de l'opération

The value is: 42
Result of the closure: 42
  • 1
  • 2

Dans cet exemple nous définissons une fonction générique call_once, qui accepte un type de F Paramètresf,dans F Doit être atteintFnOnce trait.cela signifief est une fermeture qui accepte les paramètres vides et renvoie le typeT Le résultat de.

exister main Dans la fonction, on crée une fermetureconsume, qui capture value de propriété.Ensuite, nous appelonscall_once fonction, transmiseconsume Fermeture.call_once La fonction appelle la fermeture et renvoie son résultat.parce queconsume La fermeture a été consomméevalue, réessaye d'appeler call_once Passer la même fermeture entraînera une erreur de compilation.

Voici une utilisation FnMut Exemples de traits.

fn apply_mut<F, T>(func: &mut F, num: i32) -> T
where
    F: FnMut(i32) -> T, // F 是一个可变闭包,接受一个 i32 类型的参数并返回类型为 T 的结果
{
    func(num) // 调用闭包并返回结果
}

fn main() {
    let mut count = 0;

    // 创建一个闭包,它接受一个 i32 类型的参数并将其加到 count 上
    let mut increment = |num: i32| -> i32 {
        count += num;
        count
    };

    // 使用 apply_mut 函数和 increment 闭包的引用
    let result: i32 = apply_mut(&mut increment, 5);
    println!("Result after applying increment: {}", result);

    // 再次使用 apply_mut 函数和 increment 闭包的引用
    let result: i32 = apply_mut(&mut increment, 10);
    println!("Result after applying increment again: {}", result);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

résultat de l'opération

Result after applying increment: 5
Result after applying increment again: 15
  • 1
  • 2

Dans cet exemple,apply_mut La fonction accepte unF référence mutable de type&mut F comme paramètre (donc,increment La fermeture ne bouge pas et peut être utilisée plusieurs fois) et uni32 paramètres de typenumwhere la clause préciseF Doit être une implémentationFnMut fermeture, qui accepte uni32 paramètres de type et renvoie un type deT Le résultat de.main Une fermeture mutable est créée dans la fonctionincrement, qui modifie une variable capturée count, puis utilisez apply_mut fonction pour appeler cette fermeture.chaque appelapply_mut Utiliseraincrement fermeture, etincrement Les fermetures seront modifiéescount valeur.

À Rouille,Fn Le trait signifie qu'une fermeture ne s'approprie pas les variables qu'elle capture, ni ne mute ces variables.L'exemple suivant montre comment définir une acceptationFn Clôture comme argument d'une fonction et utilisez-la dans des scénarios simultanés.

use std::thread;

// 定义一个函数,它接受一个实现了 Fn(i32) -> i32 的闭包,并调用它
fn call_once<F, T>(func: F) -> T
where
    F: Fn(i32) -> T, // 指定 F 是一个接受 i32 并返回 T 的 Fn 闭包
{
    let result = func(42); // 调用闭包,传入一个 i32 类型的值
    result // 返回闭包的执行结果
}

fn main() {
    // 定义一个简单的 Fn 闭包,它接受一个 i32 类型的参数并返回两倍的该值
    let double = |x: i32| -> i32 {
        x * 2
    };

    // 创建多个线程,每个线程都使用相同的闭包
    let handles: Vec<_> = (0..5).map(|i| {
        let func = double; // 闭包可以被复制,因为它是 Fn 类型的
        thread::spawn(move || {
            let result = call_once(func); // 调用 call_once 函数,并传入闭包
            println!("Thread {} result: {}", i, result); // 打印结果
        })
    }).collect();

    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }
}
  • 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

Toutes les exécutions ne se déroulent pas dans cet ordre. Le thread qui s'exécute en premier dépend de la planification actuelle du système.

résultat de l'opération

Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
  • 1
  • 2
  • 3
  • 4
  • 5

Dans cet exemple :

  1. Nous avons défini un call_once fonction, qui accepte un paramètre génériqueF,et F doit être satisfaitFn(i32) -> T limites des traits.cela signifieF est une fermeture qui accepte uni32 paramètres de type et renvoie un type deT Le résultat de.
  2. exister main Dans la fonction, on définit une fermeture simpledouble, qui accepte un i32 paramètres de typex et retourx * 2 Le résultat de.
  3. Nous utilisons map 5 threads sont créés et chaque thread est copiédouble Clôture et appelez-le dans un nouveau fil de discussioncall_once fonction, en passant la fermeture comme argument àcall_once
  4. Dans chaque fil,call_once La fonction est appelée, la fermeture est exécutée et le résultat est imprimé.
  5. Enfin, nous utilisons join La méthode attend que tous les threads soient terminés.

2. Itérateur

Les itérateurs permettent d'effectuer certains traitements sur les éléments d'une séquence. Ensuite, nous introduisons principalement l'utilisation d'itérateurs pour traiter les séquences d'éléments et la comparaison des performances des boucles et des itérateurs. Dans Rust, les itérateurs sont paresseux, ce qui signifie qu’aucune opération n’est effectuée jusqu’à ce que la méthode qui consomme l’itérateur soit appelée.

2.1 Trait d'itérateur et méthode suivante

Les itérateurs implémentent tous une fonction appelée Iterator est défini dans le trait de la bibliothèque standard. La définition de ce trait ressemble à ceci :

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
    // 此处省略了方法的默认实现
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

type Item etSelf::Item , ils définissent le type associé au trait.Mais pour l'instant, tout ce que vous devez savoir, c'est que ce code montre comment implémenterIterator Les traits exigent qu'unItem tapez, ceciItem le type est utilisé commenext Le type de valeur de retour de la méthode. autrement dit,Item Le type sera le type de l'élément renvoyé par l'itérateur.

next OuiIterator Les seules méthodes que les implémenteurs doivent définir.next Renvoie un élément à la fois dans l'itérateur, encapsulé dansSome , lorsque l'itérateur se termine, il renvoieNone

2.2 Méthodes de consommation des itérateurs

Iterator les traits ont un ensemble de méthodes différentes qui sont implémentées par défaut et fournies par la bibliothèque standard ;Iterator Toutes ces méthodes se trouvent dans la documentation de l'API de la bibliothèque standard du trait.Certaines méthodes appellent dans leur définitionnext méthode, c'est pourquoi en mettant en œuvreIterator le trait doit être implémenténext raisons de méthode.

Ces appels next Les méthodes des méthodes sont appelées adaptateurs consommateurs car leur appel consomme des itérateurs.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    // 使用into_iter()将Vec转换为消费迭代器
    let numbers_iter = numbers.iter();
    let mut sum: i32 = numbers_iter
        // 使用sum适配器计算迭代器中所有元素的总和
        .sum();

    // 打印总和
    println!("The sum is: {}", sum);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Si vous l'utilisez à nouveau numbers_iter Une erreur sera signalée.sum La méthode s'approprie l'itérateur et l'appelle à plusieurs reprisesnext pour parcourir l'itérateur, consommant ainsi l'itérateur. Au fur et à mesure qu'il parcourt chaque élément, il ajoute chaque élément à une somme et renvoie la somme une fois l'itération terminée.transfertsum N'est plus autorisé à être utilisénumbers_iter, parce que j'appelle sum lorsqu'il prend possession de l'itérateur.

2.3 Méthodes pour générer d'autres itérateurs

Iterator Une autre classe de méthodes définies dans traits, appelées adaptateurs d'itérateur, nous permet de changer l'itérateur actuel en un itérateur d'un type différent. Plusieurs adaptateurs d’itérateurs peuvent être appelés dans une chaîne. Cependant, comme tous les itérateurs sont paresseux, une méthode d’adaptateur consommatrice doit être appelée afin d’obtenir les résultats de l’appel de l’adaptateur d’itérateur.

fn main() {
    let v1: Vec<i32> = vec![1, 2, 3];
    let v2: Vec<_> = v1.iter().map(|x| x * x).collect();
    assert_eq!(v2, vec![1, 4, 9]);
}
  • 1
  • 2
  • 3
  • 4
  • 5

La méthode collect consomme l’itérateur et collecte les résultats dans une structure de données.parce quemap Obtient une fermeture qui vous permet de spécifier toutes les opérations que vous souhaitez effectuer sur chaque élément qu'elle traverse.

2.4 Utiliser des fermetures qui captent leur environnement

De nombreux adaptateurs d'itérateur acceptent les fermetures comme paramètres, et généralement la fermeture spécifiée comme paramètre d'adaptateur d'itérateur sera la fermeture qui capture son environnement.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // 使用into_iter()将Vec转换为消费迭代器
    let filtered_and_squared: Vec<i32> = numbers.into_iter()
        // 使用filter适配器过滤元素,接受一个闭包作为参数
        // 闭包捕获其环境,这里指numbers的元素
        .filter(|&x| x % 2 == 0) // 保留偶数
        // 使用map适配器对过滤后的元素进行变换,也接受一个闭包
        .map(|x| x * x) // 对每个元素进行平方
        // 使用collect适配器将结果收集到一个新的Vec中
        .collect();

    // 打印过滤和平方后的结果
    println!("The filtered and squared numbers are: {:?}", filtered_and_squared);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

résultat de l'opération

The filtered and squared numbers are: [4, 16]
  • 1

Dans cet exemple,filter etmap Les deux sont des adaptateurs d’itérateurs, ils acceptent les fermetures comme paramètres. Ces fermetures capturent leur environnement, c'est-à-dire les éléments de l'itérateur.existerfilter Dans adaptateur, fermeture|&x| x % 2 == 0 Utilisé pour vérifier si l'élément est un nombre pair, si c'est le cas, l'élément sera conservé.existermap Dans adaptateur, fermeture|x| x * x Utilisé pour mettre au carré chaque élément. Étant donné que ces adaptateurs sont consommateurs, ils consomment l’itérateur d’origine et ne peuvent donc pas être réutilisés. enfin,collect L'adaptateur collecte les éléments traités dans un nouveauVec milieu.

2.5 Comparaison des performances de l'itérateur Loop VS

Les itérateurs, en tant qu'abstraction de haut niveau, sont compilés en code avec à peu près les mêmes performances que le code de bas niveau écrit à la main. Les itérateurs sont l'une des abstractions à coût nul de Rust, ce qui signifie que l'abstraction n'introduit aucune surcharge d'exécution.

fn main() {
    let numbers1 = (0..1000000).collect::<Vec<i64>>();
    let numbers2 = (0..1000000).collect::<Vec<i64>>();

    let mut sum1 = 0i64;
    let mut sum2 = 0i64;

    // 测量for循环的性能
    let start = std::time::Instant::now();
    for val in numbers1 {
        sum1 += val;
    }
    let loop_duration = start.elapsed();

    // 测量迭代器的性能
    let start = std::time::Instant::now();
    for val in numbers2.iter() {
        sum2 += val;
    }
    let iterator_duration = start.elapsed();

    println!("Iterator took: {:?}", iterator_duration);
    println!("For loop took: {:?}", loop_duration);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

résultat de l'opération

Iterator took: 12.796012ms
For loop took: 11.559512ms

Iterator took: 12.817732ms
For loop took: 11.687655ms

Iterator took: 12.75484ms
For loop took: 11.89468ms

Iterator took: 12.812022ms
For loop took: 11.785106ms

Iterator took: 12.78293ms
For loop took: 11.528941ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

À partir de cet exemple, vous pouvez voir que les itérateurs sont encore légèrement plus lents.

Lien de référence

  1. Site officiel de Rust :https://www.rust-lang.org/zh-CN
  2. Documentation officielle de Rust :https://doc.rust-lang.org/
  3. Jeu de rouille :https://play.rust-lang.org/
  4. "Langage de programmation Rust"