Closurelar: Environmentni qamrab oladigan anonim funksiyalar

Rustning closureri - bu o'zgaruvchida saqlashingiz yoki boshqa funksiyalarga argument sifatida o'tishingiz mumkin bo'lgan anonim funktsiyalar. Closureni bir joyda yaratishingiz va keyin uni boshqa kontekstda baholash uchun boshqa joyga murojaat qilishingiz mumkin. Funksiyalardan farqli o'laroq, closurelar ular belgilangan doiradagi qiymatlarni olishlari mumkin. Ushbu closure xususiyatlari kodni qayta ishlatish va xatti-harakatlarni moslashtirishga(behavior customization) qanday imkon berishini ko'rsatamiz.

Environmentni closurelar bilan qo'lga olish

Avvalo, keyinchalik foydalanish uchun ular belgilangan muhitdan(environment) qiymatlarni olish uchun closurelardan qanday foydalanishimiz mumkinligini ko'rib chiqamiz.Bu senariy: Ko'pincha bizning futbolka kompaniyamiz reklama ro'yxatidagi kimgadir eksklyuziv, cheklangan nashrdagi futbolkani sovg'a sifatida taqdim etadi. Pochta ro'yxatidagi odamlar ixtiyoriy ravishda o'z profillariga sevimli ranglarini qo'shishlari mumkin. Agar bepul futbolka uchun tanlangan kishi o'zining sevimli ranglar to'plamiga ega bo'lsa, u rangdagi futbolkani oladi. Agar biror kishi sevimli rangni ko'rsatmagan bo'lsa, u kompaniyada eng ko'p bo'lgan rangni oladi.

Buni amalga oshirishning ko'plab usullari mavjud. Ushbu misol uchun biz Qizil va Moviy variantlariga ega FutbolkaRangi nomli enumdan foydalanamiz (oddiylik uchun mavjud ranglar sonini cheklaydi). Biz kompaniya inventarini Inventarizatsiya strukturasi bilan ifodalaymiz, unda futbolkalar deb nomlangan maydon mavjud bo‘lib, unda hozirda mavjud bo‘lgan futbolka ranglarini ifodalovchi Vec<FutbolkaRangi> mavjud. Inventarizatsiya da belgilangan yutuq metodi bepul futbolka g‘olibining ixtiyoriy futbolka rangini afzal ko‘radi va odam oladigan futbolka rangini qaytaradi. Ushbu sozlash 13-1 ro'yxatda ko'rsatilgan:

Fayl nomi: src/main.rs

#[derive(Debug, PartialEq, Copy, Clone)]
enum FutbolkaRangi {
    Qizil,
    Moviy,
}

struct Inventarizatsiya {
    futbolkalar: Vec<FutbolkaRangi>,
}

impl Inventarizatsiya {
    fn yutuq(&self, foydalanuvchi_afzalligi: Option<FutbolkaRangi>) -> FutbolkaRangi {
        foydalanuvchi_afzalligi.unwrap_or_else(|| self.most_stocked())
    }

    fn most_stocked(&self) -> FutbolkaRangi {
        let mut qizil_raqam = 0;
        let mut moviy_raqam = 0;

        for rang in &self.futbolkalar {
            match rang {
                FutbolkaRangi::Qizil => qizil_raqam += 1,
                FutbolkaRangi::Moviy => moviy_raqam += 1,
            }
        }
        if qizil_raqam > moviy_raqam {
            FutbolkaRangi::Qizil
        } else {
            FutbolkaRangi::Moviy
        }
    }
}

fn main() {
    let store = Inventarizatsiya {
        futbolkalar: vec![FutbolkaRangi::Moviy, FutbolkaRangi::Qizil, FutbolkaRangi::Moviy],
    };

    let user_pref1 = Some(FutbolkaRangi::Qizil);
    let yutuq1 = store.yutuq(user_pref1);
    println!(
        "{:?} afzalligi bilan foydalanuvchi {:?} oladi",
        user_pref1, yutuq1
    );

    let user_pref2 = None;
    let yutuq2 = store.yutuq(user_pref2);
    println!(
        "{:?} afzalligi bilan foydalanuvchi {:?} oladi",
        user_pref2, yutuq2
    );
}

Ro'yxat 13-1: Futbolka kompaniyasining sovg'a holati

main boʻlimida belgilangan dokon ikkita moviy futbolka va bitta qizil futbolka qolgan. Qizil ko'ylakni afzal ko'rgan foydalanuvchi va hech qanday imtiyozsiz foydalanuvchi uchun yutuq metodini chaqiramiz.

Shunga qaramay, ushbu kod ko'p jihatdan amalga oshirilishi mumkin va bu yerda, closurelarga e'tibor qaratish uchun biz siz allaqachon o'rgangan tushunchalarga yopishib oldik, closuredan foydalanadigan yutuq metodidan tashqari. yutuq metodida biz Option<FutbolkaRangi> turidagi parametr sifatida foydalanuvchi imtiyozini olamiz va foydalanuvchi_afzalligi da unwrap_or_else metodini chaqiramiz. Option<T> da unwrap_or_else metodi standart kutubxona tomonidan aniqlanadi. Buning uchun bitta argument kerak bo‘ladi: T qiymatini qaytaruvchi hech qanday argumentsiz closure (Option<T> enumning Some variantida, bizning holatimizda FutbolkaRangida tugaydigan qiymat turiga aylantiriladi). Agar Option<T> Some varianti bo'lsa, unwrap_or_else qiymatini Some ichidan qaytaradi. Agar Option<T> None varianti bo'lsa, unwrap_or_else closureni chaqiradi va closure orqali qaytarilgan qiymatni qaytaradi.

Biz closure ifodasini belgilaymiz || self.most_stocked()ni unwrap_or_else argumenti sifatida. Bu hech qanday parametrlarni o'zi qabul qilmaydigan closuredir (agar closure parametrlari bo'lsa, ular ikkita vertikal chiziq orasida paydo bo'ladi). Closurening asosiy qismi self.most_stocked() ni chaqiradi. Biz bu yerda closureni aniqlayapmiz va unwrap_or_else ni amalga oshirish, agar natija kerak bo‘lsa, keyinroq closureni baholaydi.

Ushbu kodni ishga tushirsak quyidagi natijani chop etadi:

$ cargo run
   Compiling shirt-company v0.1.0 (file:///projects/shirt-company)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/shirt-company`
The user with preference Some(Red) gets Red
The user with preference None gets Blue

Qiziqarli tomoni shundaki, biz joriy Inventarizatsiya misolida self.most_stocked() deb nomlanuvchi closuredan o‘tdik. Standart kutubxona biz belgilagan Inventarizatsiya yoki FutbolkaRangi turlari yoki biz ushbu senariyda foydalanmoqchi bo'lgan mantiq haqida hech narsa bilishi shart emas edi. Closure self Inventarizatsiya misoliga o'zgarmas(immutable) referenceni oladi va uni biz belgilagan kod bilan unwrap_or_else metodiga uzatadi. Funksiyalar esa o'z muhitini(environmentini) shu tarzda ushlab tura olmaydi.

Closure typi Inference va Annotation

Funksiyalar va closurelar o'rtasida ko'proq farqlar mavjud. Closurelar odatda parametrlar turlarini yoki fn funksiyalari kabi qaytarish qiymatini(return value) izohlashni talab qilmaydi. Funksiyalar uchun tur annotationlari talab qilinadi, chunki turlar foydalanuvchilarga ochiq interfeysning bir qismidir. Ushbu interfeysni qat'iy belgilash, har bir kishi funksiya qanday turdagi qiymatlardan foydalanishi va qaytarishi(return) haqida kelishib olishini ta'minlash uchun muhimdir. Boshqa tomondan, closurelar bu kabi ochiq interfeysda ishlatilmaydi: ular o'zgaruvchilarda saqlanadi va ularni nomlamasdan va kutubxonamiz foydalanuvchilariga ko'rsatmasdan foydalaniladi.

Closurelar odatda qisqa va har qanday ixtiyoriy senariyda emas, faqat tor kontekstda tegishli. Ushbu cheklangan kontekstlarda kompilyator ko'pgina o'zgaruvchilarning turlarini qanday aniqlashga qodir bo'lganiga o'xshab, parametrlarning turlarini va qaytish turini taxmin qilishi mumkin (kompilyatorga closure turi annotationlari ham kerak bo'lgan kamdan-kam holatlar mavjud).

O'zgaruvchilarda bo'lgani kabi, agar biz aniqlik va ravshanlikni oshirishni xohlasak, zarur bo'lgandan ko'ra batafsilroq bo'lish uchun turdagi annotationlarni qo'shishimiz mumkin. Closure uchun turlarga izoh(annotation) qo'yish 13-2 ro'yxatda ko'rsatilgan definitionga o'xshaydi. Ushbu misolda biz closureni aniqlaymiz va uni 13-1 ro'yxatda bo'lgani kabi argument sifatida topshirgan joyda closureni belgilash o'rniga uni o'zgaruvchida saqlaymiz.

Fayl nomi: src/main.rs

use std::thread;
use std::time::Duration;

fn generate_workout(intensity: u32, random_number: u32) {
    let expensive_closure = |num: u32| -> u32 {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure(intensity));
        println!("Next, do {} situps!", expensive_closure(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_closure(intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

Ro'yxat 13-2: Ixtiyoriy turdagi annotationlarni qo'shish closureda parametr va qaytariladigan qiymat turlari

Turga annotationlar qoʻshilishi bilan closure sintaksisi funksiyalar sintaksisiga koʻproq oʻxshaydi. Bu yerda biz taqqoslash uchun parametriga 1 qo'shadigan funksiyani va bir xil xatti-harakatlarga ega bo'lgan closureni aniqlaymiz. Tegishli qismlarni bir qatorga qo'yish uchun bir nechta bo'shliqlar qo'shdik. Bu pipelardan foydalanish va ixtiyoriy bo'lgan sintaksis miqdori bundan mustasno, closure sintaksisi funksiya sintaksisiga qanchalik o'xshashligini ko'rsatadi:

fn  bitta_v1_qoshish    (x: u32) -> u32 { x + 1 }
let bitta_v2_qoshish =  |x: u32| -> u32 { x + 1 };
let bitta_v3_qoshish =  |x|             { x + 1 };
let bitta_v4_qoshish =  |x|               x + 1  ;

Birinchi qatorda funksiya taʼrifi(definition), ikkinchi qatorda esa toʻliq izohlangan closure definitioni koʻrsatilgan. Uchinchi qatorda biz closure definitiondan turdagi annotationlarni olib tashlaymiz. To'rtinchi qatorda biz qavslarni olib tashlaymiz, ular ixtiyoriy, chunki closure tanas(body) faqat bitta ifodaga(expression) ega. Bularning barchasi to'g'ri definitionlar bo'lib, ular chaqirilganda bir xil xatti-harakatlarni keltirib chiqaradi. bitta_v3_qoshish va bitta_v4_qoshish qatorlari kompilyatsiya qilish uchun closurelarni baholashni talab qiladi, chunki turlar ulardan foydalanishdan kelib chiqadi. Bu let v = Vec::new(); ga o'xshash bo'lib, Rust turini aniqlay olishi uchun Vec ga turiga izohlar(annotation) yoki ba'zi turdagi qiymatlar kiritilishi kerak.

Closure definitionlari uchun kompilyator ularning har bir parametri va ularning qaytish(return) qiymati uchun bitta aniq turdagi xulosa chiqaradi. Masalan, 13-3 ro'yxatda parametr sifatida qabul qilingan qiymatni qaytaradigan qisqa closure definitioni ko'rsatilgan. Ushbu closure ushbu misol maqsadlaridan tashqari juda foydali emas. E'tibor bering, biz definitionga hech qanday annotation qo'shmaganmiz. Hech qanday turdagi annotationlar mavjud emasligi sababli, biz bu yerda birinchi marta String bilan qilgan har qanday turdagi closureni chaqirishimiz mumkin. Agar biz namuna_closure ni butun(integer) son bilan chaqirishga harakat qilsak, xatoga yo'l qo'yamiz.

Fayl nomi: src/main.rs

fn main() {
    let namuna_closure = |x| x;

    let s = namuna_closure(String::from("salom"));
    let n = namuna_closure(5);
}

Ro'yxat 13-3: Ikki xil turga ega bo'lgan closureni chaqirishga urinish

Kompilyator bizga quyidagi xatoni beradi:

$ cargo run
   Compiling namuna_closure v0.1.0 (file:///projects/namuna_closure)
error[E0308]: mismatched types
 --> src/main.rs:5:29
  |
5 |     let n = namuna_closure(5);
  |             --------------- ^- help: try using a conversion method: `.to_string()`
  |             |               |
  |             |               expected struct `String`, found integer
  |             arguments to this function are incorrect
  |
note: closure parameter defined here
 --> src/main.rs:2:28
  |
2 |     let namuna_closure = |x| x;
  |                            ^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `namuna_closure` due to previous error

Birinchi marta namuna_closure String qiymati bilan chaqirilganda, kompilyator x turini va closurening qaytish turini String deb hisoblaydi. Keyin bu turlar(type) namuna_closure bo'limida yopiladi va biz bir xil closure(yopilish) bilan boshqa turdan foydalanishga uringanimizda xatoga duch kelamiz.

Malumot olish yoki Egalik(Ownership) huquqini ko'chirish

Closurelar o'z muhitidan qiymatlarni uchta usulda olishlari mumkin, ular to'g'ridan-to'g'ri funksiya parametr olishi mumkin bo'lgan uchta usulga mos keladi: immutably borrowing (o'zgarmas borrowing(qarz olish)), mutably borrowing (o'zgaruvchan borrowing(qarz olish)) va egalik qilish(ownership). Closure funksiya tanasi(body) olingan qiymatlar bilan nima qilishiga qarab ulardan qaysi birini ishlatishni hal qiladi.

13-4 ro'yxatda biz list deb nomlangan vectorga immutable(o'zgarmas) referencei qamrab oluvchi closureni aniqlaymiz, chunki u qiymatni chop etish uchun faqat immutable referencega muhtoj:

Fayl nomi: src/main.rs

fn main() {
    let list = vec![1, 2, 3];
    println!("Closureni belgilashdan oldin: {:?}", list);

    let faqat_borrow = || println!("Closuredan: {:?}", list);

    println!("Closureni chaqirishdan oldin: {:?}", list);
    faqat_borrow();
    println!("Chaqirilgandan keyin closure: {:?}", list);
}

Ro'yxat 13-4: Buni closureni aniqlash va chaqirish immutable referenceni ushlaydi

Ushbu misol, shuningdek, o'zgaruvchining closure definitioniga bog'lanishi mumkinligini ko'rsatadi va biz keyinchalik o'zgaruvchi nomi va qavslar yordamida o'zgaruvchi nomi funksiya nomiga o'xshab yopishni chaqirishimiz mumkin.

Biz bir vaqtning o'zida bir nechta immutable(o'zgarmas) referencelarga ega bo'lishimiz mumkin bo'lgan list uchun, list closure definitionidan oldin, closure definitionidan keyin, lekin closure chaqirilishidan oldin va closure chaqirilgandan keyin hali ham koddan foydalanish mumkin. Ushbu kod kompilyatsiya bo'ladi, ishlaydi va chop etadi:

$ cargo run
   Compiling namuna_closure v0.1.0 (file:///projects/namuna_closure)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/namuna_closure`
Closureni belgilashdan oldin: [1, 2, 3]
Closureni chaqirishdan oldin: [1, 2, 3]
Closuredan: [1, 2, 3]
Chaqirilgandan keyin closure: [1, 2, 3]

Keyinchalik, 13-5 ro'yxatda biz closure bodysini list vectoriga element qo'shishi uchun o'zgartiramiz. Closure endi mutable(o'zgaruvchan) referenceni oladi:

Fayl nomi: src/main.rs

fn main() {
    let mut list = vec![1, 2, 3];
    println!("Closureni aniqlashdan oldin: {:?}", list);

    let mut ozgaruvchan_borrow = || list.push(7);

    ozgaruvchan_borrow();
    println!("Chaqirilgandan keyin closure: {:?}", list);
}

Ro'yxat 13-5: Mutable(o'zgaruvchan) referenceni ushlaydigan closureni aniqlash va chaqirish

Ushbu kod kompilyatsiya bo'ladi, ishlaydi va chop etadi:

$ cargo run
   Compiling namuna_closure v0.1.0 (file:///projects/namuna_closure)
    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
     Running `target/debug/namuna_closure`
Closureni aniqlashdan oldin: [1, 2, 3]
Chaqirilgandan keyin closure: [1, 2, 3, 7]

E'tibor bering, ozgaruvchan_borrow closurening ta'rifi(definition) va chaqiruvi o'rtasida endi println! belgisi yo'q: ozgaruvchan_borrow aniqlanganda, u listga o'zgaruvchan(mutable) referenceni oladi. Closure chaqirilgandan keyin biz closureni qayta ishlatmaymiz, shuning uchun mutable borrow(o'zgaruvchan qarz) tugaydi. Closure definationi va closure chaqiruvi o'rtasida chop etish uchun immutable(o'zgarmas) borrowga ruxsat berilmaydi, chunki mutable borrow mavjud bo'lganda boshqa borrowlarga ruxsat berilmaydi. Qaysi xato xabari borligini bilish uchun u yerga println! qo'shib ko'ring!

Agar closurening asosiy qismi ownershipga(egalik) muhtoj bo'lmasa ham, uni environmentda foydalanadigan qiymatlarga ownershiplik qilishga harakat qilmoqchi bo'lsangiz, parametrlar ro'yxatidan oldin move kalit so'zidan foydalanishingiz mumkin.

Ushbu uslub asosan ma'lumotlarni yangi threadga tegishli bo'lishi uchun ko'chirish uchun yangi threadga closureni o'tkazishda foydalidir. Biz 16-bobda parallellik(concurrency) haqida gapirganda, thereadlarni va nima uchun ulardan foydalanishni xohlashingizni batafsil muhokama qilamiz, ammo hozircha move kalit so'ziga muhtoj bo'lgan closure yordamida yangi threadni yaratishni qisqacha ko'rib chiqamiz. 13-6 ro'yxat vektorni asosiy thredda emas, balki yangi threadda chop etish uchun o'zgartirilgan 13-4 ro'yxatini ko'rsatadi:

Fayl nomi: src/main.rs

use std::thread;

fn main() {
    let list = vec![1, 2, 3];
    println!("Closureni aniqlashdan oldin: {:?}", list);

    thread::spawn(move || println!("{:?} threaddan", list))
        .join()
        .unwrap();
}

Roʻyxat 13-6: listga ownershiplik qilish uchun threadni yopishni majburlash uchun move dan foydalanish

Biz argument sifatida ishlash uchun threadni yopish(closure) imkonini berib, yangi threadni yaratamiz. Closure tanasi(body) listni chop etadi. Roʻyxat 13-4, closure faqat oʻzgarmas(immutable) reference yordamida listni yozib oldi, chunki bu uni chop etish uchun zarur boʻlgan listga kirishning eng kam miqdori. Ushbu misolda, closure tanasi(body) hali ham faqat o'zgarmas(immutable) referencega muhtoj bo'lsa ham, biz closure definationing boshiga move kalit so'zini qo'yish orqali list closurega ko'chirilishi kerakligini ko'rsatishimiz kerak. Yangi thread asosiy threadning qolgan qismi tugashidan oldin tugashi yoki asosiy thread birinchi bo'lib tugashi mumkin. Agar asosiy thread listga ownershiplikni saqlab qolgan boʻlsa-da, lekin yangi thread paydo boʻlishidan oldin tugasa va listni tashlab qoʻysa, threaddagi immutable(oʻzgarmas) reference yaroqsiz boʻladi. Shuning uchun, kompilyator listni yangi threadga berilgan closurega ko'chirishni talab qiladi, shuning uchun reference haqiqiy bo'ladi. Kompilyatorda qanday xatolarga yo'l qo'yganingizni ko'rish uchun closure aniqlangandan so'ng, move kalit so'zini olib tashlang yoki asosiy threaddagi list dan foydalaning!

Qabul qilingan qiymatlarni closuredan va Fn traitlaridan ko'chirish

Closure ma'lumotnomani qo'lga kiritgandan so'ng(shunday qilib, agar biror narsa bo'lsa, closurega ko'chirilgan narsaga ta'sir qiladi) yoki closure aniqlangan environmentdan qiymatga ownershiplikni qo'lga kiritgandan so'ng,(agar biror narsa bo'lsa, closuredan ko'chirilgan narsaga ta'sir qiladi) closurening asosiy qismidagi kod closure keyinroq baholanganda referencelar yoki qiymatlar bilan nima sodir bo'lishini belgilaydi.

Closure tanasi(body) quyidagilardan birini amalga oshirishi mumkin: olingan qiymatni closuredan tashqariga ko'chirish(move), olingan qiymatni mutatsiyalash, qiymatni ko'chirish yoki mutatsiyalash yoki boshlash uchun environmentdan hech narsa olmaslik.

Yopishning environmentdan handlelarni ushlash(capture) va boshqarish usuli closure implementlarining qaysi traitlariga ta'sir qiladi va traitlar funksiyalar va structlar qanday closure turlaridan foydalanishi mumkinligini ko'rsatishi mumkin. Closurelar ushbu Fn belgilarining bittasi, ikkitasi yoki uchtasini avtomatik ravishda qo'shimcha usulda, closure tanasi qiymatlarni(value) qanday boshqarishiga qarab implement qilinadi:

  1. FnOnce bir marta chaqirilishi mumkin bo'lgan closurelar uchun amal qiladi. Barcha closurelar hech bo'lmaganda ushbu traitni amalga oshiradi(implement qiladi), chunki barcha closurelar chaqirilishi mumkin. Qabul qilingan qiymatlarni(value) tanasidan tashqariga ko'chiradigan closure faqat FnOnce ni implement qiladi va boshqa Fn traitlarining hech birini implement qilmaydi, chunki uni faqat bir marta chaqirish mumkin.
  2. FnMut qo'lga kiritilgan qiymatlarni(value) tanasidan tashqariga olib chiqmaydigan, lekin olingan qiymatlarni o'zgartirishi mumkin bo'lgan closurelarga nisbatan qo'llaniladi.Ushbu closurelarni bir necha marta chaqirish mumkin.
  3. Fn qo'lga kiritilgan qiymatlarni tanasidan tashqariga chiqarmaydigan va olingan qiymatlarni o'zgartirmaydigan closurelar, shuningdek, environmentdan hech narsani ushlab(capture) turmaydigan closurelar uchun amal qiladi. Ushbu closurelar environmentni o'zgartirmasdan bir necha marta chaqirilishi mumkin, bu bir vaqtning o'zida bir necha marta closureni chaqirish kabi holatlarda muhimdir.

Keling, 13-1 ro'yxatda biz qo'llagan Option<T> bo'yicha unwrap_or_else metodining definitionini ko'rib chiqaylik:

impl<T> Option<T> {
    pub fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: FnOnce() -> T
    {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
}

Eslatib oʻtamiz, T Optionning Some variantidagi qiymat turini ifodalovchi umumiy turdir(generic type). Bu T turi, shuningdek, unwrap_or_else funksiyasining qaytish(return) turidir: masalan, Option<String>da unwrap_or_else ni chaqiruvchi kod, String oladi.

Keyin, unwrap_or_else funksiyasi qo'shimcha F umumiy turdagi parametrga ega ekanligiga e'tibor bering. F turi f nomli parametrning turi(type) bo'lib, biz unwrap_or_else ga chaqiruv(call) qilganimizda ta`minlovchi closuredir.

Generic F turida belgilangan belgi FnOnce() -> T bo'lib, bu F bir marta chaqirilishi, hech qanday argumentga ega bo'lmasligi va T qaytarilishini bildiradi. Trait bound-da FnOnce dan foydalanish unwrap_or_else faqat bir marta f ni chaqirishi mumkin bo'lgan cheklovni ifodalaydi. unwrap_or_else matnida biz Option Some bo‘lsa, f chaqirilmasligini ko‘rishimiz mumkin. Agar Option None bo'lsa, f bir marta chaqiriladi. Barcha closurelar FnOnce ni implement qilganligi sababli, unwrap_or_else eng har xil turdagi closurelarni qabul qiladi va imkon qadar moslashuvchan.

Eslatma: Funksiyalar uchta Fn traitlarini ham implement qilishi mumkin. Agar biz qilmoqchi bo'lgan narsa environmentdan qiymat olishni(*capture value) talab qilmasa, biz Fn traitlaridan birini implement qiladigan narsa kerak bo'lganda closure o'rniga funksiya nomidan foydalanishimiz mumkin. Masalan, Option<Vec<T>> qiymatida, agar qiymat None bo'lsa, yangi, bo'sh vektorni olish uchun unwrap_or_else(Vec::new) ni chaqirishimiz mumkin.

Endi keling, slicelarda aniqlangan standart kutubxona metodini ko‘rib chiqamiz, bu unwrap_or_elsedan qanday farq qilishini va nima uchun sort_by_key trait bound uchun FnOnce o‘rniga FnMut dan foydalanishini ko‘raylik. Closure ko'rib chiqilayotgan qismdagi joriy elementga reference ko'rinishida bitta argument oladi va order qilinishi mumkin bo'lgan K turidagi qiymatni qaytaradi. Ushbu funksiya har bir elementning ma'lum bir atributi bo'yicha sliceni saralashni xohlaganingizda foydalidir. 13-7 ro'yxatda bizda Kvadrat misollar listi mavjud va biz ularni kenglik atributi bo'yicha pastdan yuqoriga tartiblash uchun sort_by_key dan foydalanamiz:

Fayl nomi: src/main.rs

#[derive(Debug)]
struct Kvatrat {
    kengligi: u32,
    balandligi: u32,
}

fn main() {
    let mut list = [
        Kvatrat { kengligi: 10, balandligi: 1 },
        Kvatrat { kengligi: 3, balandligi: 5 },
        Kvatrat { kengligi: 7, balandligi: 12 },
    ];

    list.sort_by_key(|r| r.kengligi);
    println!("{:#?}", list);
}

Ro'yxat 13-7: Kvadratlarlarni kengligi bo'yicha tartiblash uchun sort_by_key dan foydalaning

Ushbu kod quyidagi natijani chop etadi:

$ cargo run
   Compiling Kvadrats v0.1.0 (file:///projects/Kvadrats)
    Finished dev [unoptimized + debuginfo] target(s) in 0.41s
     Running `target/debug/Kvadratlar`
[
    Kvadrat {
        kengligi: 3,
        balandligi: 5,
    },
    Kvadrat {
        kengligi: 7,
        balandligi: 12,
    },
    Kvadrat {
        kengligi: 10,
        balandligi: 1,
    },
]

sort_by_key FnMut closureni olish uchun aniqlanganining sababi shundaki, u closureni bir necha marta chaqiradi: slicedagi har bir element uchun bir marta. |r| r.kengligi o'z environmentidan hech narsani ushlamaydi(capture), mutatsiyaga uchramaydi yoki boshqa joyga ko'chirmaydi, shuning uchun u trait bound bo'lgan talablarga javob beradi.

Bundan farqli o'laroq, 13-8 ro'yxat faqat FnOnce traitini amalga oshiradigan closure misolini ko'rsatadi, chunki u qiymatni environmentdan tashqariga ko'chiradi. Kompilyator bu closureni sort_by_key bilan ishlatishimizga ruxsat bermaydi:

Fayl nomi: src/main.rs

#[derive(Debug)]
struct Rectangle {
    kengligi: u32,
    balandligi: u32,
}

fn main() {
    let mut list = [
        Rectangle { kengligi: 10, balandligi: 1 },
        Rectangle { kengligi: 3, balandligi: 5 },
        Rectangle { kengligi: 7, balandligi: 12 },
    ];

    let mut saralash_operatsiyalari = vec![];
    let qiymat = String::from("chaqirilgan kalit orqali");

    list.sort_by_key(|r| {
        saralash_operatsiyalari.push(qiymat);
        r.kengligi
    });
    println!("{:#?}", list);
}

Ro'yxat 13-8: sort_by_key yordamida FnOnce closuredan foydalanishga urinish

Bu listni saralashda sort_by_key necha marta chaqirilishini hisoblashning oʻylab topilgan (bu ishlamaydi) usulidir. Ushbu kod closure environmentidan qiymat—a String ni saralash_operatsiyalari vektoriga surish(push) orqali hisoblashni amalga oshirishga harakat qiladi. Closure qiymatni ushlaydi, so‘ngra qiymat ownershipligini saralash_operatsiyalari vektoriga o‘tkazish orqali qiymatni closuredan chiqaradi. Ushbu closureni bir marta chaqirish mumkin; uni ikkinchi marta chaqirishga urinish ishlamaydi, chunki qiymat endi saralash_operatsiyalari ga push qilinadigan environmentda(muhitda) bo'lmaydi! Shuning uchun, bu closure faqat FnOnce ni amalga oshiradi(implement qiladi). Ushbu kodni kompilyatsiya qilmoqchi bo'lganimizda, biz qiymat ni closuredan chiqarib bo'lmaydigan xatoni olamiz, chunki closure FnMut ni implement qilishi kerak:

$ cargo run
   Compiling kvadratlar v0.1.0 (file:///projects/kvadratlar)
error[E0507]: cannot move out of `qiymat`, a captured variable in an `FnMut` closure
  --> src/main.rs:18:30
   |
15 |     let qiymat = String::from("chaqirilgan kalit orqali");
   |         ----- captured outer variable
16 |
17 |     list.sort_by_key(|r| {
   |                      --- captured by this `FnMut` closure
18 |         saralash_operatsiyalari.push(qiymat);
   |                              ^^^^^ move occurs because `qiymat` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `kvadratlar` due to previous error

Xato qiymatni environmentdan tashqariga olib chiqadigan closure tanasidagi(body) chiziqqa(line) ishora qiladi. Buni tuzatish uchun biz closure tanasini qiymatlarni environmentdan ko'chirmasligi uchun o'zgartirishimiz kerak. sort_by_key necha marta chaqirilishini hisoblash uchun hisoblagichni(counter) environment saqlash va uning qiymatini closure tanasida oshirish buni hisoblashning yanada sodda usuli hisoblanadi. 13-9 ro'yxatdagi closure sort_by_key bilan ishlaydi, chunki u faqat raqam_saralash_operatsiyalari counteriga mutable(o'zgaruvchan) referenceni oladi va shuning uchun uni bir necha marta chaqirish mumkin:

Fayl nomi: src/main.rs

#[derive(Debug)]
struct Kvadrat {
    kengligi: u32,
    balandligi: u32,
}

fn main() {
    let mut list = [
        Kvadrat { kengligi: 10, balandligi: 1 },
        Kvadrat { kengligi: 3, balandligi: 5 },
        Kvadrat { kengligi: 7, balandligi: 12 },
    ];

    let mut raqam_saralash_operatsiyalari = 0;
    list.sort_by_key(|r| {
        raqam_saralash_operatsiyalari += 1;
        r.kengligi
    });
    println!("{:#?}, {raqam_saralash_operatsiyalari} operatsiyalarida tartiblangan", list);
}

Roʻyxat 13-9: sort_by_key bilan FnMut closuredan foydalanishga ruxsat berilgan

Fn traitlari closurelardan foydalanadigan funksiyalar yoki turlarni belgilash yoki ishlatishda muhim ahamiyatga ega. Keyingi bo'limda biz iteratorlarni muhokama qilamiz. Ko'pgina iterator metodlari closure argumentlarini oladi, shuning uchun biz davom etayotganda ushbu closure tafsilotlarini(details) yodda tuting!