Condivisione della tecnologia

Apprendimento del linguaggio di programmazione Rust - caratteristiche del linguaggio funzionale: iteratori e chiusure

2024-07-12

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

Ruggine Una chiusura è una funzione anonima che può essere salvata in una variabile o passata come argomento ad altre funzioni. È possibile creare una chiusura in un posto e quindi eseguire operazioni di chiusura in un contesto diverso. A differenza delle funzioni, le chiusure consentono di acquisire valori nell'ambito in cui sono definiti.

Un iteratore è responsabile della logica di attraversare ogni elemento della sequenza e di determinare quando termina la sequenza. Quando si utilizzano gli iteratori, non è necessario reimplementare questa logica.

1. Chiusura

Chiusura, una struttura simile a una funzione che può essere memorizzata in una variabile.

1.1 Le chiusure catturano il loro ambiente

In Rust, le chiusure sono funzioni anonime che catturano variabili dall'ambiente esterno. Il comportamento di acquisizione di una chiusura dipende dai tipi di variabili e dal modo in cui la chiusura utilizza tali variabili.

Cattura per valore (per valore)

Quando una chiusura acquisisce una variabile in base al valore, ne assume la proprietà. Ciò significa che dopo aver creato la chiusura, la variabile originale non può più essere utilizzata.

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

Cattura per riferimento (per riferimento)

Quando una chiusura cattura una variabile per riferimento, prende in prestito quella variabile. Ciò significa che la variabile originale è ancora disponibile, ma la chiusura può solo prenderla in prestito, non acquisirne la proprietà.

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

Cattura mutevole

Una chiusura può acquisire un riferimento mutabile, consentendogli di modificare il valore della variabile originale.

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

Se la chiusura viene rimossa mut Modifica, compilazione fallisce.

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

Viene visualizzato un messaggio di errore: A causa del prestito di una variabile count,trasferimento closure È richiesto il legame mutabile.

   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

chiusura come parametro

Le chiusure possono essere utilizzate comePassaggio dei parametriPer altre funzioni, acquisisci variabili nell'ambiente.

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

In questo esempio:

  1. Definiamo una chiusura square, che cattura per riferimento main in funzionenumber variabile e calcolarne il quadrato.
  2. Definiamo un file chiamato call_closure funzione, che accetta aFn() -> i32 Le chiusure legate ai tratti accettano parametri, il che significa che la chiusura non accetta parametri e restituisce ai32 tipo di valore.
  3. Noi chiamiamo call_closure funzione e volontàsquare Le chiusure vengono passate come argomenti.Perchésquare Le chiusure non hanno parametri, possono essere passate direttamente come parametri acall_closure
  4. call_closure La funzione richiama la chiusura e stampa il risultato.

1.2 Inferenza e annotazioni sul tipo di chiusura

Ci sono molte più differenze tra funzioni e chiusure.Le chiusure non sempre richiedono qualcosa di similefn Specificare i tipi di parametri e restituire valori come funzioni. Le annotazioni di tipo sono richieste nelle funzioni perché fanno parte dell'interfaccia esplicita esposta all'utente. È importante definire rigorosamente queste interfacce per garantire che tutti abbiano una comprensione coerente delle funzioni utilizzate e dei tipi di valori restituiti. Al contrario, le chiusure non vengono utilizzate per tali interfacce esposte: vengono archiviate in variabili e utilizzate senza nominarle o esporle agli utenti della libreria.

Le chiusure sono generalmente brevi e si riferiscono a un contesto ristretto piuttosto che a qualsiasi situazione arbitraria. In questi contesti ristretti, il compilatore può dedurre in modo affidabile i tipi di parametri e valori restituiti, in modo simile a come può dedurre i tipi della maggior parte delle variabili (sebbene vi siano rari casi in cui il compilatore richiede annotazioni sul tipo di chiusura).

Analogamente alle variabili, è possibile aggiungere annotazioni di tipo se desideriamo aumentare la chiarezza e la chiarezza, con lo svantaggio di rendere il codice più dettagliato (rispetto a quanto strettamente necessario).

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

Con le annotazioni di tipo, la sintassi delle chiusure è più simile alle funzioni. Ecco un confronto verticale tra la definizione di una funzione che aggiunge uno al suo argomento e la sintassi di chiusura che ha lo stesso comportamento. Qui sono stati aggiunti alcuni spazi per allineare le parti corrispondenti. Questo mostra come la sintassi di chiusura sia simile alla sintassi delle funzioni, ad eccezione dell'uso di pipe e di alcune sintassi opzionali:

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

Quindi l'esempio sopra può essere semplificato in:

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

Il compilatore deduce un tipo concreto per ogni parametro e valore restituito nella definizione di chiusura.

Diamo un’occhiata a un altro esempio:

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

Tieni presente che questa definizione di chiusura non aggiunge alcuna annotazione di tipo, quindi possiamo chiamare questa chiusura con qualsiasi tipo. Ma se provi a chiamare la chiusura due volte, la prima volta usando i32 e la seconda volta usando f32, verrà segnalato un errore:Previsto, poiché la chiusura era stata precedentemente chiamata con un argomento di tipo "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 Spostare i valori catturati fuori dalle chiusure e dai tratti Fn

Una volta che una chiusura acquisisce un riferimento o la proprietà di un valore nell'ambiente in cui è definita (influenzando così ciò che, se presente, viene spostato nella chiusura), il codice all'interno del corpo della chiusura definisce il riferimento successivamente quando la chiusura viene valutato. O come viene manipolato il valore (che influisce su cosa, se non altro, viene rimosso dalla chiusura). Il corpo della chiusura può eseguire una delle seguenti operazioni: spostare un valore acquisito fuori dalla chiusura, modificare il valore acquisito, non spostare né modificare il valore oppure non acquisire mai il valore dall'ambiente in primo luogo.

Il modo in cui una chiusura cattura e gestisce i valori nell'ambiente influenza i tratti implementati dalla chiusura. I tratti sono un modo per le funzioni e le strutture di specificare i tipi di chiusure che possono utilizzare.A seconda di come il corpo della chiusura gestisce i valori, la chiusura ne implementa automaticamente e in modo incrementale uno, due o treFn tratto.

  1. FnOnce Si applica alle chiusure che possono essere chiamate una volta. Tutte le chiusure implementano almeno questa caratteristica perché tutte le chiusure possono essere chiamate.Una chiusura che sposta i valori catturati fuori dal corpo della chiusura viene solo implementataFnOnce tratto perché può essere chiamato solo una volta.
  2. FnMut Adatto per chiusure che non spostano il valore catturato fuori dal corpo della chiusura, ma possono modificare il valore catturato. Questo tipo di chiusura può essere richiamata più volte.
  3. Fn Si applica alle chiusure che non spostano il valore catturato fuori dal corpo della chiusura né modificano il valore catturato e ovviamente includono chiusure che non catturano il valore dall'ambiente. Tali chiusure possono essere chiamate più volte senza modificare l'ambiente, il che è importante negli scenari in cui le chiusure vengono chiamate più volte contemporaneamente.

Ecco un esempio che mostra come usarlo in una funzione FnOnce come vincolo generico e garantire che la chiusura venga chiamata solo una volta:

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

risultato dell'operazione

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

In questo esempio definiamo una funzione generica call_once, che accetta un tipo di F Parametrif,In F Deve essere raggiuntoFnOnce tratto.questo significaf è una chiusura che accetta parametri vuoti e restituisce il tipoT il risultato di.

esistere main Nella funzione creiamo una chiusuraconsume, che cattura value di proprietà.Quindi chiamiamocall_once funzione, passataconsume Chiusura.call_once La funzione chiama la chiusura e restituisce il risultato.Perchéconsume La chiusura è stata consumatavalue, prova a chiamare di nuovo call_once Passare nella stessa chiusura comporterà un errore di compilazione.

Ecco un utilizzo FnMut Esempi di tratti.

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

risultato dell'operazione

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

In questo esempio,apply_mut La funzione accetta aF riferimento mutabile di tipo&mut F come parametro (quindi,increment La chiusura non verrà spostata e potrà essere utilizzata più volte) e ai32 parametri di tiponumwhere clausola specificaF Deve essere un'implementazioneFnMut chiusura, che accetta ai32 parametri di tipo e restituisce un tipo diT il risultato di.main Nella funzione viene creata una chiusura modificabileincrement, che modifica una variabile catturata count, quindi utilizzare apply_mut funzione per chiamare questa chiusura.ogni chiamataapply_mut Useràincrement chiusura eincrement Le chiusure verranno modificatecount valore.

Nella ruggine,Fn La caratteristica significa che una chiusura non assume la proprietà delle variabili che cattura, né le modifica.L'esempio seguente mostra come definire un'accettazioneFn Chiusura come argomento di una funzione e utilizzo in scenari simultanei.

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

Non tutte le esecuzioni sono in questo ordine. Quale thread viene eseguito per primo dipende dalla pianificazione corrente del sistema.

risultato dell'operazione

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

In questo esempio:

  1. Abbiamo definito a call_once funzione, che accetta un parametro genericoF,E F deve essere soddisfattoFn(i32) -> T limiti dei tratti.questo significaF è una chiusura che accetta ai32 parametri di tipo e restituisce un tipo diT il risultato di.
  2. esistere main Nella funzione definiamo una chiusura semplicedouble, che accetta a i32 parametri di tipox e ritornox * 2 il risultato di.
  3. Noi usiamo map Vengono creati 5 thread e ogni thread viene copiatodouble Chiudi e chiamalo in un nuovo threadcall_once funzione, passando la chiusura come argomentocall_once
  4. In ogni thread,call_once Viene chiamata la funzione, viene eseguita la chiusura e il risultato viene stampato.
  5. Infine, usiamo join Il metodo attende il completamento di tutti i thread.

2. Iteratore

Gli iteratori consentono di eseguire determinate elaborazioni sugli elementi di una sequenza. Successivamente, introduciamo principalmente l'uso degli iteratori per elaborare sequenze di elementi e il confronto delle prestazioni degli iteratori loop VS. In Rust, gli iteratori sono pigri, il che significa che nessuna operazione viene eseguita finché non viene chiamato il metodo che consuma l'iteratore.

2.1 Tratto dell'iteratore e metodo successivo

Tutti gli iteratori implementano una funzione chiamata Iterator è definito nel tratto della libreria standard. La definizione di questo tratto è la seguente:

pub trait Iterator {
    type Item;

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

type Item ESelf::Item , definiscono il tipo associato al tratto.Ma per ora tutto ciò che devi sapere è che questo codice mostra come implementareIterator I tratti richiedono che aItem tipo, questoItem il tipo è usato comenext Il tipo di valore restituito del metodo. in altre parole,Item Il tipo sarà il tipo dell'elemento restituito dall'iteratore.

nextIterator Gli unici implementatori del metodo devono definire.next Restituisce un elemento alla volta nell'iteratore, incapsulato inSome , quando l'iteratore termina, ritornaNone

2.2 Metodi di consumo degli iteratori

Iterator i tratti hanno una serie di metodi diversi implementati per impostazione predefinita forniti dalla libreria standard;Iterator Tutti questi metodi si trovano nella documentazione API della libreria standard del tratto.Alcuni metodi richiamano la loro definizionenext metodo, motivo per cui nell'implementazioneIterator caratteristica è necessaria per essere implementatanext ragioni di metodo.

Queste chiamate next I metodi dei metodi sono chiamati adattatori di consumo perché la loro chiamata consuma gli iteratori.

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

Se lo usi di nuovo numbers_iter Verrà segnalato un errore.sum Il metodo assume la proprietà dell'iteratore e lo chiama ripetutamentenext per scorrere l'iteratore, consumando così l'iteratore. Mentre esegue l'iterazione su ciascun elemento, aggiunge ogni elemento a una somma e restituisce la somma al termine dell'iterazione.trasferimentosum Non è più consentito l'usonumbers_iter, perché chiama sum quando assume la proprietà dell'iteratore.

2.3 Metodi per generare altri iteratori

Iterator Un'altra classe di metodi definiti nei tratti, chiamati adattatori iteratori, ci consente di modificare l'iteratore corrente in un iteratore di tipo diverso. È possibile richiamare più adattatori iteratori in una catena. Tuttavia, poiché tutti gli iteratori sono pigri, è necessario chiamare un metodo dell'adattatore che utilizza per ottenere i risultati della chiamata dell'adattatore dell'iteratore.

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

Il metodo Collect utilizza l'iteratore e raccoglie i risultati in una struttura dati.Perchémap Ottiene una chiusura che consente di specificare le operazioni che si desidera eseguire su ciascun elemento che attraversa.

2.4 Usare chiusure che catturino il loro ambiente

Molti adattatori iteratori accettano chiusure come parametri e solitamente la chiusura specificata come parametro dell'adattatore iteratore sarà la chiusura che cattura il relativo ambiente.

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

risultato dell'operazione

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

In questo esempio,filter Emap Entrambi sono adattatori iteratori, accettano chiusure come parametri. Queste chiusure catturano il loro ambiente, ovvero gli elementi nell'iteratore.esisterefilter Nell'adattatore, chiusura|&x| x % 2 == 0 Utilizzato per verificare se l'elemento è un numero pari, in tal caso l'elemento verrà mantenuto.esisteremap Nell'adattatore, chiusura|x| x * x Utilizzato per quadrare ciascun elemento. Poiché questi adattatori sono consumer, consumano l'iteratore originale e pertanto non possono essere riutilizzati. infine,collect L'adattatore raccoglie gli elementi elaborati in un nuovoVec mezzo.

2.5 Confronto delle prestazioni dell'iteratore Loop VS

Gli iteratori, in quanto astrazione di alto livello, vengono compilati nel codice con all'incirca le stesse prestazioni del codice di basso livello scritto a mano. Gli iteratori sono una delle astrazioni a costo zero di Rust, il che significa che l'astrazione non introduce alcun sovraccarico di runtime.

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

risultato dell'operazione

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

Come puoi vedere da questo esempio, gli iteratori sono ancora leggermente più lenti.

Collegamento di riferimento

  1. Sito ufficiale di Rust:Italiano: https://www.rust-lang.org/zh-CN
  2. Documentazione ufficiale di Rust:Italiano: https://doc.rust-lang.org/
  3. Gioco di ruggine:Italiano: https://play.rust-lang.org/
  4. "Linguaggio di programmazione Rust"