Berbagi teknologi

Pembelajaran bahasa pemrograman Rust - fitur bahasa fungsional: iterator dan penutupan

2024-07-12

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

Karat Penutupan adalah fungsi anonim yang dapat disimpan dalam variabel atau diteruskan sebagai argumen ke fungsi lain. Anda dapat membuat penutupan di satu tempat dan kemudian melakukan operasi penutupan dalam konteks berbeda. Tidak seperti fungsi, penutupan memungkinkan menangkap nilai-nilai dalam lingkup di mana mereka didefinisikan.

Sebuah iterator bertanggung jawab atas logika melintasi setiap item dalam urutan dan menentukan kapan urutan berakhir. Saat menggunakan iterator, kita tidak perlu menerapkan kembali logika ini.

1. Penutupan

Penutupan, struktur mirip fungsi yang dapat disimpan dalam variabel.

1.1 Penutupan menangkap lingkungannya

Di Rust, penutupan adalah fungsi anonim yang menangkap variabel dari lingkungan eksternal. Perilaku penangkapan suatu penutupan bergantung pada jenis variabel dan cara penutupan menggunakan variabel tersebut.

Tangkap berdasarkan nilai (Berdasarkan Nilai)

Ketika penutupan menangkap variabel berdasarkan nilai, maka diperlukan kepemilikan atas variabel tersebut. Artinya setelah penutupan dibuat, variabel asli tidak dapat digunakan lagi.

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

Tangkap dengan referensi (Dengan Referensi)

Ketika penutupan menangkap variabel dengan referensi, ia meminjam variabel tersebut. Artinya variabel aslinya masih tersedia, namun penutupan hanya dapat meminjamnya, tidak dapat mengambil kepemilikan.

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

Tangkapan yang Dapat Diubah

Penutupan dapat menangkap referensi yang bisa diubah, memungkinkannya mengubah nilai variabel asli.

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

Jika penutupannya dilepas mut Modifikasi, kompilasi gagal.

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

Pesan kesalahan muncul: Karena meminjam variabel count,transfer closure Pengikatan yang dapat diubah diperlukan.

   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

penutupan sebagai argumen

Penutupan dapat digunakan sebagaiPengoperan parameterUntuk fungsi lainnya, tangkap variabel di lingkungan.

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

Dalam contoh ini:

  1. Kami mendefinisikan penutupan square, yang menangkap dengan referensi main dalam fungsinumber variabel dan hitung kuadratnya.
  2. Kami mendefinisikan file bernama call_closure fungsi yang menerima aFn() -> i32 Penutupan terikat sifat mengambil parameter, yang berarti penutupan tidak mengambil parameter dan mengembalikan ai32 jenis nilai.
  3. Kami memanggil call_closure fungsi dan kemauansquare Penutupan diteruskan sebagai argumen.Karenasquare Penutupan tidak memiliki parameter, dapat diteruskan langsung sebagai parametercall_closure
  4. call_closure Fungsi ini memanggil penutupan dan mencetak hasilnya.

1.2 Inferensi dan anotasi jenis penutupan

Masih banyak lagi perbedaan antara fungsi dan penutupan.Penutupan tidak selalu memerlukan hal seperti itufn Tentukan jenis parameter dan kembalikan nilai seperti fungsi. Anotasi jenis diperlukan dalam fungsi karena merupakan bagian dari antarmuka eksplisit yang diekspos kepada pengguna. Penting untuk mendefinisikan antarmuka ini secara ketat untuk memastikan bahwa setiap orang memiliki pemahaman yang konsisten tentang fungsi yang digunakan dan jenis nilai yang dikembalikan. Sebaliknya, penutupan tidak digunakan untuk antarmuka terbuka seperti itu: penutupan disimpan dalam variabel dan digunakan tanpa memberi nama atau memaparkannya kepada pengguna perpustakaan.

Penutupan biasanya singkat dan berhubungan dengan konteks yang sempit dan bukan pada situasi yang sewenang-wenang. Dalam konteks terbatas ini, kompiler dapat dengan andal menyimpulkan jenis parameter dan nilai kembalian, serupa dengan cara ia dapat menyimpulkan jenis sebagian besar variabel (walaupun ada kasus yang jarang terjadi di mana kompiler memerlukan anotasi tipe penutupan).

Mirip dengan variabel, anotasi tipe dapat ditambahkan jika kita ingin meningkatkan kejelasan dan kejelasan, dengan kelemahan membuat kode lebih bertele-tele (dibandingkan dengan keharusan).

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

Dengan anotasi tipe, sintaksis penutupan lebih mirip dengan fungsi. Berikut adalah perbandingan vertikal definisi fungsi yang menambahkan satu ke argumennya dan sintaks penutupan yang memiliki perilaku yang sama. Beberapa ruang telah ditambahkan di sini untuk menyelaraskan bagian-bagian yang sesuai. Ini menunjukkan betapa miripnya sintaks penutupan dengan sintaks fungsi, kecuali untuk penggunaan pipa dan beberapa sintaks opsional:

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

Jadi contoh di atas dapat disederhanakan menjadi:

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

Kompilator menyimpulkan tipe konkret untuk setiap parameter dan mengembalikan nilai dalam definisi penutupan.

Mari kita lihat contoh lainnya:

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

Perhatikan bahwa definisi penutupan ini tidak menambahkan anotasi tipe apa pun, jadi kita dapat memanggil penutupan ini dengan tipe apa pun. Namun jika Anda mencoba memanggil penutupan dua kali, pertama kali menggunakan i32, dan kedua kali menggunakan f32, kesalahan akan dilaporkan:Diharapkan, karena penutupan sebelumnya disebut dengan argumen tipe "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 Pindahkan nilai yang diambil dari penutupan dan sifat Fn

Ketika suatu penutupan menangkap referensi, atau kepemilikan, suatu nilai dalam lingkungan di mana ia didefinisikan (sehingga mempengaruhi apa, jika ada, yang dipindahkan ke dalam penutupan), kode dalam badan penutupan mendefinisikan referensi tersebut nanti ketika penutupan dievaluasi. Atau bagaimana nilai dimanipulasi (yang mempengaruhi apa, jika ada, yang dihapus dari penutupan). Badan penutupan dapat melakukan salah satu hal berikut: memindahkan nilai yang diambil dari penutupan, mengubah nilai yang ditangkap, tidak memindahkan atau mengubah nilai, atau tidak pernah mengambil nilai dari lingkungan sejak awal.

Cara penutupan menangkap dan menangani nilai-nilai di lingkungan mempengaruhi sifat-sifat yang diterapkan oleh penutupan. Sifat adalah cara fungsi dan struktur menentukan jenis penutupan yang dapat digunakan.Bergantung pada bagaimana badan penutupan menangani nilai, penutupan secara otomatis dan bertahap mengimplementasikan satu, dua, atau tigaFn sifat.

  1. FnOnce Berlaku untuk penutupan yang dapat dipanggil satu kali. Semua penutupan menerapkan setidaknya sifat ini karena semua penutupan dapat dipanggil.Penutupan yang memindahkan nilai yang diambil keluar dari badan penutupan hanya dapat diterapkanFnOnce sifat karena hanya dapat dipanggil satu kali.
  2. FnMut Cocok untuk penutupan yang tidak memindahkan nilai yang ditangkap keluar dari badan penutupan, namun dapat mengubah nilai yang ditangkap. Jenis penutupan ini dapat dipanggil beberapa kali.
  3. Fn Berlaku untuk penutupan yang tidak memindahkan nilai yang ditangkap keluar dari badan penutupan atau mengubah nilai yang ditangkap, dan tentu saja mencakup penutupan yang tidak menangkap nilai dari lingkungan. Penutupan tersebut dapat dipanggil beberapa kali tanpa mengubah lingkungannya, yang penting dalam skenario di mana penutupan dipanggil beberapa kali secara bersamaan.

Berikut adalah contoh yang menunjukkan cara menggunakannya dalam suatu fungsi FnOnce sebagai batasan umum dan memastikan bahwa penutupan hanya dipanggil sekali:

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

hasil operasi

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

Dalam contoh ini kita mendefinisikan fungsi generik call_once, yang menerima tipe F Parameterf,di dalam F Harus dicapaiFnOnce sifat.ini berartif adalah penutupan yang menerima parameter kosong dan tipe pengembalianT hasil dari.

ada main Dalam fungsinya, kita membuat penutupanconsume, yang menangkap value kepemilikan.Lalu, kami meneleponcall_once fungsi, diteruskanconsume Penutup.call_once Fungsi tersebut memanggil penutupan dan mengembalikan hasilnya.Karenaconsume Penutupan telah dikonsumsivalue, coba telepon lagi call_once Melewati penutupan yang sama akan menghasilkan kesalahan kompilasi.

Ini kegunaannya FnMut Contoh sifat.

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

hasil operasi

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

Dalam contoh ini,apply_mut Fungsi menerima aF referensi tipe yang bisa berubah&mut F sebagai parameter (jadi,increment Penutupnya tidak akan dipindahkan dan dapat digunakan berkali-kali) dan ai32 ketik parameternumwhere klausa menentukanF Harus menjadi implementasiFnMut penutupan, yang menerima ai32 ketik parameter dan kembalikan tipeT hasil dari.main Penutupan yang bisa diubah dibuat dalam fungsi tersebutincrement, yang mengubah variabel yang ditangkap count, lalu gunakan apply_mut berfungsi untuk memanggil penutupan ini.setiap panggilanapply_mut Akan menggunakanincrement penutupan, danincrement Penutupan akan diubahcount nilai.

di karat,Fn Sifat tersebut berarti bahwa penutupan tidak mengambil kepemilikan atas variabel yang ditangkapnya, juga tidak mengubah variabel tersebut.Contoh berikut menunjukkan cara mendefinisikan penerimaanFn Penutupan sebagai argumen terhadap suatu fungsi dan menggunakannya dalam skenario bersamaan.

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

Tidak setiap proses berjalan dalam urutan ini. Thread mana yang berjalan terlebih dahulu bergantung pada penjadwalan sistem saat ini.

hasil operasi

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

Dalam contoh ini:

  1. Kami mendefinisikan a call_once fungsi, yang menerima parameter generikF,Dan F harus puasFn(i32) -> T batas sifat.ini berartiF adalah penutupan yang menerima ai32 ketik parameter dan kembalikan tipeT hasil dari.
  2. ada main Dalam fungsinya, kami mendefinisikan penutupan sederhanadouble, yang menerima a i32 ketik parameterx dan kembalix * 2 hasil dari.
  3. Kita gunakan map 5 utas dibuat dan setiap utas disalindouble Penutupan dan panggil di thread barucall_once fungsi, meneruskan penutupan sebagai argumencall_once
  4. Di setiap utas,call_once Fungsinya dipanggil, penutupan dijalankan, dan hasilnya dicetak.
  5. Akhirnya, kami menggunakan join Metode menunggu semua thread selesai.

2. Iterator

Iterator memungkinkan Anda melakukan pemrosesan tertentu pada item suatu urutan. Selanjutnya, kami terutama memperkenalkan penggunaan iterator untuk memproses urutan elemen dan perbandingan kinerja loop VS iterator. Di Rust, iteratornya malas, yang berarti tidak ada operasi yang dilakukan sampai metode yang menggunakan iterator tersebut dipanggil.

2.1 Sifat Iterator dan metode selanjutnya

Semua iterator mengimplementasikan fungsi yang disebut Iterator didefinisikan dalam sifat perpustakaan standar. Definisi dari sifat ini terlihat seperti ini:

pub trait Iterator {
    type Item;

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

type Item DanSelf::Item , mereka menentukan tipe terkait sifat tersebut.Namun untuk saat ini yang perlu Anda ketahui adalah kode ini menunjukkan cara penerapannyaIterator Sifat mengharuskan aItem ketik, iniItem tipe digunakan sebagainext Tipe nilai kembalian metode. dengan kata lain,Item Tipenya akan menjadi tipe elemen yang dikembalikan oleh iterator.

next YaIterator Satu-satunya pelaksana metode yang perlu didefinisikan.next Mengembalikan satu item pada satu waktu di iterator, dienkapsulasi dalamSome , ketika iterator berakhir, ia kembaliNone

2.2 Metode penggunaan iterator

Iterator ciri-ciri memiliki serangkaian metode berbeda yang diterapkan secara default yang disediakan oleh perpustakaan standar;Iterator Semua metode ini ditemukan dalam dokumentasi API perpustakaan standar sifat tersebut.Beberapa metode memanggil definisinyanext metode, itulah sebabnya dalam penerapannyaIterator sifat tersebut perlu diterapkannext alasan metode.

Panggilan-panggilan ini next Metode dari metode disebut menggunakan adaptor karena memanggilnya menggunakan iterator.

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

Jika Anda menggunakannya lagi numbers_iter Kesalahan akan dilaporkan.sum Metode mengambil kepemilikan atas iterator dan memanggilnya berulang kalinext untuk mengulangi iterator, sehingga memakan iterator. Saat melakukan iterasi pada setiap item, ia menambahkan setiap item ke suatu jumlah dan mengembalikan jumlah tersebut ketika iterasi selesai.transfersum Tidak lagi diperbolehkan untuk digunakannumbers_iter, karena menelepon sum ketika ia mengambil kepemilikan iterator.

2.3 Metode untuk menghasilkan iterator lain

Iterator Kelas metode lain yang didefinisikan dalam ciri, disebut adaptor iterator, memungkinkan kita mengubah iterator saat ini menjadi iterator dengan tipe berbeda. Beberapa adaptor iterator dapat dipanggil dalam satu rantai. Namun, karena semua iterator malas, metode adaptor yang memakan waktu harus dipanggil untuk mendapatkan hasil dari panggilan adaptor iterator.

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

Metode pengumpulan menggunakan iterator dan mengumpulkan hasilnya ke dalam struktur data.Karenamap Mendapat penutupan yang memungkinkan Anda menentukan operasi apa pun yang ingin Anda lakukan pada setiap elemen yang dilintasinya.

2.4 Menggunakan penutupan yang menangkap lingkungannya

Banyak adaptor iterator menerima penutupan sebagai parameter, dan biasanya penutupan yang ditentukan sebagai parameter adaptor iterator akan menjadi penutupan yang menangkap lingkungannya.

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

hasil operasi

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

Dalam contoh ini,filter Danmap Keduanya adalah adaptor iterator, mereka menerima penutupan sebagai parameter. Penutupan ini menangkap lingkungannya, yaitu elemen di iterator.adafilter Di adaptor, penutupan|&x| x % 2 == 0 Digunakan untuk memeriksa apakah suatu elemen bilangan genap, jika demikian maka elemen tersebut akan dipertahankan.adamap Di adaptor, penutupan|x| x * x Digunakan untuk mengkuadratkan setiap elemen. Karena adaptor ini bersifat konsumen, mereka menggunakan iterator asli dan oleh karena itu tidak dapat digunakan lagi. akhirnya,collect Adaptor mengumpulkan elemen yang diproses menjadi elemen baruVec tengah.

2.5 Perbandingan kinerja loop VS iterator

Iterator, sebagai abstraksi tingkat tinggi, dikompilasi menjadi kode dengan kinerja yang kira-kira sama dengan kode tingkat rendah yang ditulis tangan. Iterator adalah salah satu abstraksi tanpa biaya Rust, yang berarti abstraksi tersebut tidak menimbulkan overhead 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

hasil operasi

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

Seperti yang Anda lihat dari contoh ini, iterator masih sedikit lebih lambat.

Tautan referensi

  1. Situs web resmi karat:https://www.rust-lang.org/id-CN
  2. Dokumentasi resmi karat:https://doc.rust-lang.org/
  3. Bermain Karat:https://play.rust-lang.org/
  4. "Bahasa Pemrograman Rust"