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
);
}
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 FutbolkaRangi
da 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); }
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);
}
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); }
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); }
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 list
ga 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(); }
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 list
ni yozib oldi, chunki bu uni chop etish uchun zarur boʻlgan list
ga 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 list
ga ownershiplikni saqlab qolgan boʻlsa-da, lekin yangi thread paydo boʻlishidan oldin tugasa va list
ni tashlab qoʻysa, threaddagi immutable(oʻzgarmas) reference yaroqsiz boʻladi. Shuning uchun, kompilyator list
ni 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:
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 faqatFnOnce
ni implement qiladi va boshqaFn
traitlarining hech birini implement qilmaydi, chunki uni faqat bir marta chaqirish mumkin.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.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
Option
ning 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, bizFn
traitlaridan birini implement qiladigan narsa kerak bo'lganda closure o'rniga funksiya nomidan foydalanishimiz mumkin. Masalan,Option<Vec<T>>
qiymatida, agar qiymatNone
bo'lsa, yangi, bo'sh vektorni olish uchununwrap_or_else(Vec::new)
ni chaqirishimiz mumkin.
Endi keling, slicelarda aniqlangan standart kutubxona metodini ko‘rib chiqamiz, bu unwrap_or_else
dan 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); }
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);
}
Bu list
ni 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 qiymat
ni ushlaydi, so‘ngra qiymat
ownershipligini saralash_operatsiyalari
vektoriga o‘tkazish orqali qiymat
ni 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 qiymat
ni 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); }
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!