تمثّل المغلّفات في لغة رست دوالًا مجهولة anonymous يمكنك حفظها في متغير أو تمريرها مثل وسيط إلى دالة أخرى، ويمكنك إنشاء مغلف في مكان ما، ثم استدعاءه من مكان آخر ليُفَّذ بحسب سياق المكان، وعلى عكس الدوال فالمغلفات يمكنها الوصول إلى القيم الموجودة في النطاق المعرفة بها، وسنوضح كيف تسمح لنا مزايا المغلفات بإعادة استخدام شيفرتنا البرمجية وتخصيص سلوكها.
الحصول على المعلومات من البيئة باستخدام المغلفات
سنفحص أولًا كيفية استخدام المغلفات للحصول على القيم من البيئة التي عرّفناها فيها لاستخدام لاحق. إليك حالة استخدام ممكنة: لدينا شركة لبيع القمصان ونمنح شخصًا ما على قائمة مراسلة البريد الإلكتروني قميصًا حصريًا بين الحين والآخر مثل ترويج لشركتنا، ويمكن أن يُضيف الأشخاص على قائمة المراسلة لونهم المفضل إلى ملفهم بصورةٍ اختيارية، وإذا حدّد الشخص الذي سيحصل على قميص مجاني لونه المفضل فإنه يحصل على هذا اللون تحديدًا وإلا فإنه يحصل على اللون المتواجد بكثرة في المخزن.
هناك عدة طرق لتطبيق ذلك، إذ يمكننا على سبيل المثال استخدام معدّد enum يدعى ShirtColor
يحتوي على متغايرين variants هما Red
و Blue
(حددنا لونين فقط للبساطة). نمثّل مخزن الشركة باستخدام الهيكل Inventory
الذي يحتوي على حقل يدعى shirts
يحتوي على النوع Vec<ShirtColor>
، الذي يمثّل لون القميص الموجود حاليًا في المخزن. يحصل التابع giveaway
المُعرّف في Inventory
على لون القميص المفضّل الاختياري للمستخدم من المستخدمين الرابحين القميص مجانًا ويُعيد لون القميص الذي سيحصل عليه المستخدم. التطبيق لكل ما سبق ذكره موضح في الشيفرة 1:
اسم الملف: src/main.rss
#[derive(Debug, PartialEq, Copy, Clone)] enum ShirtColor { Red, Blue, } struct Inventory { shirts: Vec<ShirtColor>, } impl Inventory { fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor { user_preference.unwrap_or_else(|| self.most_stocked()) } fn most_stocked(&self) -> ShirtColor { let mut num_red = 0; let mut num_blue = 0; for color in &self.shirts { match color { ShirtColor::Red => num_red += 1, ShirtColor::Blue => num_blue += 1, } } if num_red > num_blue { ShirtColor::Red } else { ShirtColor::Blue } } } fn main() { let store = Inventory { shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue], }; let user_pref1 = Some(ShirtColor::Red); let giveaway1 = store.giveaway(user_pref1); println!( "The user with preference {:?} gets {:?}", user_pref1, giveaway1 ); let user_pref2 = None; let giveaway2 = store.giveaway(user_pref2); println!( "The user with preference {:?} gets {:?}", user_pref2, giveaway2 ); }
الشيفرة 1: شيفرة توزيع القمصان للمستخدمين
يحتوي المتغير store
المعرف في الدالة main
على قميصين أحدهما باللون الأزرق والآخر باللون الأحمر للتوزيع ضمن حملة التسويق هذه. نستدعي التابع giveaway
للمستخدم الذي يفضّل القميص الأحمر وللمستخدم الذي ليس لديه أي تفضيل معين.
يمكن تطبيق الشيفرة البرمجية بمختلف الطرق، إلا أننا نركز هنا على استخدام المغلفات، لذا فقد التزمنا بالمفاهيم التي تعلمتها مسبقًا باستثناء ما بداخل التابع giveaway
الذي يستخدم مغلّفًا. نحصل على تفضيل المستخدم مثل معامل من النوع Option<ShirtColor>
في التابع giveaway
ونستدعي التابع unwrap_or_else
على user_preference
. التابع unwrap_or_else
على النوع Option<T>
مُعرّف في المكتبة القياسية ويأخذ وسيطًا واحدًا ألا وهو مغلف دون أي وسطاء يعيد القيمة T
(النوع ذاته المُخزن في المتغاير Some
داخل النوع Option<T>
، وفي هذه الحالة ShirtColor
). إذا كان النوع Option<T>
هو المتغاير Some
، سيُعيد التابع unwrap_or_else
القيمة الموجودة داخل Some
، وإذا كان المتغاير داخل النوع Option<T>
هو None
، سيستدعي التابع المغلف ويُعيد القيمة المُعادة من المغلف.
نحدد تعبير المغلف بالشكل || self.most_stocked()
مثل وسيط للتابع unwrap_or_else
. لا يأخذ هذا المغلف أي معاملات، إذ نضع المعاملات بين الخطين العموديين إذا احتوى المغلف على معاملات. يستدعي متن المغلف التابع self.most_stocked()
، ونعرّف هنا المغلف بحيث يُقيّم تطبيق unwrap_or_else
المغلف لاحقًا إذا احتجنا للنتيجة.
يطبع تنفيذ الشيفرة البرمجية السابقة الخرج التالي:
$ 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
الجانب المثير للاهتمام هنا هو أننا نمرر مغلفًا يستدعي self.most_stocked()
على نسخة Inventory
الحالية. لا تحتاج المكتبة القياسية لمعرفة أي شيء بخصوص النوعين Inventory
أو ShirtColor
الذين عرّفناهما، أو المنطق الذي نريد استخدامه في هذه الحالة، إذ أن المغلف يحصل على مرجع ثابث immutable reference إلى self
الخاصة بنسخة Inventory
، ثم يمرره إلى الشيفرة البرمجية التي كتبناها داخل التابع unwrap_or_else
. إذا قارنّا هذه العملية بالتوابع، فالتوابع غير قادرة على الحصول على هذه المعلومات من البيئة بالطريقة ذاتها.
استنتاج نوع المغلف وتوصيفه
هناك المزيد من الاختلافات بين الدوال والمغلفات، إذ لا تتطلب المغلفات منك عادةً توصيف أنواع المعاملات، أو نوع القيمة المُعادة مثلما تتطلب الدوال fn
ذلك، والسبب في ضرورة تحديد الأنواع في الدوال هو لأن الأنواع جزءٌ من واجهة صريحة مكشوفة لمستخدميك وتعريف الواجهة بصورةٍ دقيقة مهمٌ للتأكد من أن الجميع متفقٌ على أنواع القيم التي تستخدمها الدالة وتعيدها، بينما لا تُستخدم المغلّفات في الواجهات المكشوفة بهذه الطريقة، إذ أنها تُخزّن في متغيرات دون تسميتها وكشفها لمستخدمي مكتبتك.
تكون المغلفات عادةً قصيرة وترتبط بسياق معين ضيق بدلًا من حالة عامة اعتباطية، ويمكن للمصرّف في هذا السياق المحدود استنتاج أنواع المعاملات والقيمة المعادة بصورةٍ مشابهة لاستنتاجه لمعظم أنواع المتغيرات، وهناك حالات نادرة يحتاج فيها المصرف وجود توصيف للأنواع في المغلفات أيضًا.
يمكننا إضافة توصيف النوع إذا أردنا لزيادة دقة ووضوح المغلف كما هو الحال عند تعريف المتغيرات، إلا أن هذه الطريقة تتطلب كتابةً أطول غير ضرورية. يبدو توصيف الأنواع في المغلفات مثل التعريف الموجود في الشيفرة 2، ونعرّف في هذا المثال مغلّفًا في النقطة التي نمرّر فيها وسيطًا كما فعلنا في الشيفرة 1.
اسم الملف: src/main.rs
let expensive_closure = |num: u32| -> u32 { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num };
الشيفرة 2: إضافة توصيف اختياري لأنواع المعاملات والقيمة المُعادة في المغلف
تبدو طريقة كتابة المغلفات مشابهة لطريقة كتابة الدوال بعد إضافة توصيف النوع، إذ نعرّف هنا دالةً تجمع 1 إلى المعامل ومغلّفًا بالسلوك ذاته للمقارنة بين الاثنين، ويوضح ذلك كيف أن طريقة كتابة المغلفات مشابهة لطريقة كتابة الدوال باستثناء استخدام الرمز |
وكمية الصياغة الاختيارية:
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 ;
يوضح السطر الأول تعريف دالة، بينما يوضح السطر الثاني تعريف مغلف موصّف بالكامل، ثمّ نزيل في السطر الثالث التوصيف من تعريف المغلف، ونزيل في السطر الرابع الأقواس الاختيارية لأن محتوى المغلف يتألف من تعبير واحد، وجميع التعاريف السابقة تعاريف صالحة الاستخدام تمنحنا السلوك ذاته عند استدعائها. يتطلب السطران add_one_v3
و add_one_v4
تقييم قيمة المغلف حتى يجري تصريفهما، لأن الأنواع يجب أن تُسنتج من خلال استخدامهما وهذا الأمر مشابه لحاجة السطر let v = Vec::new();
لتوصيف النوع أو إضافة قيم من نوع ما إلى Vec
، بحيث تستطيع رست استنتاج النوع.
يستنتج المصرف نوعًا واحدًا ثابتًا لكل من المعاملات في حال تعريف المغلفات، إضافةً إلى النوع المُعاد منها. على سبيل المثال، توضح الشيفرة 3 تعريف مغلف قصير يُعيد القيمة التي يتلقاها مثل معاملٍ له، وهذا المغلف ليس مفيدًا جدًا إلا أن هدفه توضيحي. لاحظ أننا لم نضِف أي توصيف للنوع في التعريف وبالتالي يمكننا استدعاء المغلف باستخدام أي نوع، وهو ما فعلناه في الشيفرة 3، إذ استدعينا المغلّف في المرة الأولى باستخدام النوع String
، إلا أننا حصلنا على خطأ عند استدعائنا للمغلف example_closure
باستخدام قيمة عدد صحيح integer.
اسم الملف: src/main.rs
let example_closure = |x| x; let s = example_closure(String::from("hello")); let n = example_closure(5);
الشيفرة 3: محاولة استدعاء مغلف يُستنتج نوعه باستخدام نوعين مختلفين
يعرض لنا المصرّف الخطأ التالي:
$ cargo run Compiling closure-example v0.1.0 (file:///projects/closure-example) error[E0308]: mismatched types --> src/main.rs:5:29 | 5 | let n = example_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 example_closure = |x| x; | ^ For more information about this error, try `rustc --explain E0308`. error: could not compile `closure-example` due to previous error
استنبط المصرّف نوع x
المُمرّر بكونه سلسلة نصية String
عندما رأى أن أول استخدام للمغلف example_closure
كان باستخدام قيمة String
، كما استنبط القيمة المعادة بالنوع String
. تُقيّد هذه الأنواع في المغلف example_closure
من تلك النقطة فصاعدًا وسنحصل على خطأ، إذا حاولنا استخدام نوع مختلف بعد ذلك في المغلف ذاته.
الحصول على المراجع أو نقل الملكية
يمكن أن تحصل المغلفات على القيم من البيئة بثلاث طرق وهي مرتبطة بالطرق الثلاث التي تستطيع فيها الدالة الحصول على القيم على أنها معاملاتها: الاستعارة الثابتة أو الاستعارة المتغيّرة أو أخذ الملكية، ويحدد المغلف واحدًا من هذه الطرق الثلاث حسب القيم الموجودة في متن الدالة.
نعرّف في الشيفرة 4 مغلفًا يحصل على مرجع ثابت لشعاع يدعى list
لأنه يحتاج فقط للمراجع الثابتة لطباعة القيمة:
اسم الملف: src/main.rs
fn main() { let list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list); let only_borrows = || println!("From closure: {:?}", list); println!("Before calling closure: {:?}", list); only_borrows(); println!("After calling closure: {:?}", list); }
الشيفرة 4: تعريف مغلف واستدعاءه، بحيث يحصل هذا المغلف على مرجع ثابت
يوضح هذا المثال أيضًا أنه من الممكن للمتغير أن يرتبط بتعريف مغلف، ويمكننا لاحقًا استدعاء المغلف باستخدام اسم المتغير والقوسين وكأن اسم المتغير يمثل اسم دالة.
يمكن الوصول للشعاع list
من الشيفرة البرمجية قبل تعريف المغلف وبعد تعريفه، شرط أن يكون قبل استدعاء المغلف وبعد استدعاء المغلف، وذلك لأنه من الممكن وجود عدّة مراجع ثابتة للشعاع list
في ذات الوقت، وتُصرَّف الشيفرة البرمجية بنجاح وتُنفَّذ وتطبع التالي:
$ cargo run Compiling closure-example v0.1.0 (file:///projects/closure-example) Finished dev [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/closure-example` Before defining closure: [1, 2, 3] Before calling closure: [1, 2, 3] From closure: [1, 2, 3] After calling closure: [1, 2, 3]
نعدّل في الشيفرة 5 متن المغلف بحيث نضيف عنصرًا إلى الشعاع list
، وبالتالي يحصل المغلف الآن على مرجع متغيّر:
اسم الملف: src/main.rs
fn main() { let mut list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list); let mut borrows_mutably = || list.push(7); borrows_mutably(); println!("After calling closure: {:?}", list); }
الشيفرة 5: تعريف واستدعاء مغلف يحتوي على مرجع متغيّر
تُصرَّف الشيفرة البرمجية بنجاح وتُنفَّذ ونحصل على الخرج التالي:
$ cargo run Compiling closure-example v0.1.0 (file:///projects/closure-example) Finished dev [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/closure-example` Before defining closure: [1, 2, 3] After calling closure: [1, 2, 3, 7]
لاحظ أننا لا نستخدم println!
بين تعريف المغلف borrows_mutably
واستدعائه، إذ يحصل المغلف على مرجع متغيّر للشعاع list
عند تعريفه، ولا نستخدم المغلف مجددًا بعد استدعائه لذا تنتهي الاستعارة المتغيرة عندها. لا يُسمح بطباعة مرجع متغيّر بين تعريف المغلف واستدعائه، إذ لا يُسمح بوجود أي عمليات استعارة أخرى عند وجود استعارة متغيّرة. حاول إضافة استدعاء للماكرو println!
لترى رسالة الخطأ التي تظهر لك.
إذا أردت إجبار المغلف على أخذ ملكية القيم التي يستخدمها في البيئة على الرغم من أن متن المغلف لا يتطلب أخذ الملكية، فيمكنك استخدام الكلمة المفتاحية move
قبل قائمة المعاملات.
هذه الطريقة مفيدة خصوصًا عند تمرير مغلف لخيط thread جديد لنقل البيانات، بحيث تُمتلك بواسطة الخيط الجديد. سنناقش الخيوط بالتفصيل وسبب استخدامنا لها لاحقًا، لكن دعنا للوقت الحالي نستكشف عملية إضافة خيط جديد باستخدام مغلّف يحتاج الكلمة المفتاحية move
. توضح الشيفرة 6 إصدارًا عن الشيفرة 4، إلا أننا نطبع الشعاع هنا في خيط جديد بدلًا من الخيط الرئيسي:
اسم الملف: src/main.rs
use std::thread; fn main() { let list = vec![1, 2, 3]; println!("Before defining closure: {:?}", list); thread::spawn(move || println!("From thread: {:?}", list)) .join() .unwrap(); }
الشيفرة 6: استخدام move
لإجبار خيط المغلف على أخذ ملكية list
نُنشئ خيطًا جديدًا وذلك بمنح الخيط مغلف ليعمل به مثل وسيط، ويطبع متن المغلف القائمة. يحصل المغلف في الشيفرة 4 على list
باستخدام مرجع ثابت لأنها تُعد أدنى درجات الوصول المطلوبة لطباعة محتويات list
، إلا أننا في هذا المثال نحدد أن list
يجب أن تُنقل إلى المغلف بإضافة الكلمة المفتاحية move
في بداية تعريف المغلّف على الرغم من أن متن المغلف يتطلب فقط مرجعًا ثابتًا. يمكن للخيط الجديد أن ينتهي من التنفيذ قبل انتهاء الخيط الرئيسي من التنفيذ أو أن ينتهي الخيط الرئيسي أولًا، وإذا احتفظ الخيط الرئيسي بملكية list
وانتهى من التنفيذ قبل الخيط الجديد وأسقط list
فهذا يعني أن المرجع الثابت في الخيط الجديد سيكون غير صالحًا، ولذلك يتطلب المصرف نقل list
إلى المغلف في الخيط الجديد بحيث يبقى المرجع صالحًا داخله. جرّب إزالة الكلمة المفتاحية move
أو استخدام list
في الخيط الرئيسي بعد تعريف المغلف لرؤية خطأ المصرف الذي يظهر لك.
نقل القيم خارج المغلفات وسمات Fn
تعرّف الشيفرة البرمجية الموجودة داخل مغلف ما الأمر الذي سيحصل للمراجع أو القيم بعد أن يُقيّم المغلف (بالتالي التعريف على الشيء الذي نُقل خارج المغلف إذا كان موجودًا) وذلك بعد أن يحصل المغلف على المرجع أو ملكية قيمة ما من البيئة مكان تعريفه (بالتالي التعريف على الشيء الذي نُقل إلى المغلف إذا كان موجودًا). يمكن لمتن المغلف أن يفعل أيًا من الأشياء التالية: نقل قيمة خارج المغلّف، أو التعديل على قيمة داخل المغلف، أو عدم نقل القيمة وعدم تعديلها، أو عدم الحصول على أي قيمة من البيئة في المقام الأول.
تؤثر الطريقة التي يحصل بها المغلف على القيم ويتعامل معها من البيئة على السمات التي يطبقها المغلف والسمات traits هي الطريقة التي تستطيع فيها كل من الدوال والهياكل تحديد نوع المغلفات الممكن استخدامها. تطبّق المغلفات تلقائيًا سمةً أو سمتين أو ثلاث من سمات Fn
التالية على نحوٍ تراكمي بحسب تعامل متن المغلف للقيم:
-
السمة
FnOnce
: تُطبَّق على جميع المغلفات الممكن استدعاؤها مرةً واحدة. تُطبّق جميع المغلفات هذه السمة على الأقل لأنه يمكن لجميع المغلفات أن تُستدعى، وسيطبق المغلف الذي ينقل القيم خارج متنه السمةFnOnce
فقط دون أي سماتFn
أخرى لأنه يُمكن استدعاءه مرةً واحدةً فقط. -
السمة
FnMut
: تُطبَّق على جميع المغلفات التي لا تنقل القيم خارج متنها، إلا أنها من الممكن أن تعدّل على هذه القيم، ويمكن استدعاء هذه المغلفات أكثر من مرة واحدة. -
السمة
Fn
: تُطبَّق على المغلفات التي لا تنقل القيم خارج متنها ولا تعدل على القيم، إضافةً إلى المغلفات التي لا تحصل على أي قيم من البيئة. يمكن لهذه المغلفات أن تُستدعى أكثر من مرة واحدة دون التعديل على بيئتها وهو أمرٌ مهم في حالة استدعاء مغلف عدة مرات على نحوٍ متعاقب.
دعنا ننظر إلى تعريف التابع unwrap_or_else
على النوع Option<T>
الذي استخدمناه في الشيفرة 1:
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(), } } }
تذكّر أن T
هو نوع معمم generic type يمثل نوع القيمة في المتغاير Some
للنوع Option
، وهذا النوع T
يمثل أيضًا نوع القيمة المعادة من الدالة unwrap_or_else
، إذ ستحصل الشيفرة البرمجية التي تستدعي unwrap_or_else
على النوع Option<String>
على النوع String
على سبيل المثال.
لاحظ تاليًا أن الدالة unwrap_or_else
تحتوي على معامل نوع معمم إضافي يُدعى F
والنوع F
هنا هو نوع المعامل ذو الاسم f
وهو المغلف الذي نمرره للدالة unwrap_or_else
عند استدعائها.
حد السمة trait bound المحدد على النوع المعمم E
هو FnOnce() -> T
، مما يعني أن النوع F
يجب أن يكون قابلًا للاستدعاء مرةً واحدة وألّا يأخذ أي وسطاء وأن يعيد قيمةً من النوع T
. يوضح استخدام FnOnce
في حد السمة القيد: أن unwrap_or_else
ستستدعي f
مرةً واحدةً على الأكثر. يمكنك من رؤية متن الدالة unwrap_or_else
معرفة أن f
لن تُستدعى إذا كان المتغاير Some
موجودًا في Option
، بينما ستُستدعى f
مرةً واحدة إذا وُجد المتغاير None
في Option
. تقبل الدالة unwrap_or_else
أنواعًا مختلفة من المغلفات بصورةٍ مرنة، لأن جميع المغلفات تطبّق السمة FnOnce
.
ملاحظة: يمكن أن تطبق الدوال سمات Fn
الثلاث أيضًا. يمكننا استخدام اسم الدالة بدلًا من مغلف عندما نريد شيئًا يطبق واحدةً من سمات Fn
إذا كان ما نريد فعله لا يتطلب الحصول على قيمة من البيئة. يمكننا على سبيل المثال، استدعاء unwrap_or_else(Vec::new)
على قيمة Option<Vec<T>>
للحصول على شعاع فارغ جديد إذا كانت القيمة هي None
.
دعنا ننظر الآن إلى تابع المكتبة القياسية sort_by_key
المعرف على الشرائح slices لرؤية الاختلاف بينه وبين unwrap_or_else
وسبب استخدام sort_by_key
للسمة FnMut
بدلًا من السمة FnOnce
لحد السمة. يحصل المغلف على وسيط واحد على هيئة مرجع للعنصر الحالي في الشريحة ويُعيد قيمةً من النوع K
يمكن ترتيبها، وهذه الدالة مفيدة عندما تريد ترتيب شرحة بسمة attribute معينة لكل عنصر. لدينا في الشيفرة 7 قائمة من نسخ instances
من الهيكل Rectangle
ونستخدم sort_by_key
لترتيبها حسب سمة width
من الأصغر إلى الأكبر:
اسم الملف: src/main.rs
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let mut list = [ Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }, ]; list.sort_by_key(|r| r.width); println!("{:#?}", list); }
الشيفرة 7: استخدام sort_by_key
لترتيب المستطيلات بحسب عرضها
نحصل على الخرج التالي مما سبق:
$ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) Finished dev [unoptimized + debuginfo] target(s) in 0.41s Running `target/debug/rectangles` [ Rectangle { width: 3, height: 5, }, Rectangle { width: 7, height: 12, }, Rectangle { width: 10, height: 1, }, ]
السبب في كون sort_by_key
معرفًا ليأخذ مغلفًا يطبق السمة FnMut
هو استدعاء الدالة للمغلف عدة مرات: مرةً واحدةً لكل عنصر في الشريحة. لا يحصل المغلف |r| r.width
على أي قيمة من البيئة أو يعدل عليها أو ينقلها لذا فهو يحقق شروط حد السمة هذه.
توضح الشيفرة 8 مثالًا لمغلف على النقيض، إذ يطبق هذا المغلف السمة FnOnce
فقط لأنه ينقل قيمة خارج البيئة، ولن يسمح لنا المصرّف باستخدام هذا المغلف مع sort_by_key
:
اسم الملف: src/main.rs
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let mut list = [ Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }, ]; let mut sort_operations = vec![]; let value = String::from("by key called"); list.sort_by_key(|r| { sort_operations.push(value); r.width }); println!("{:#?}", list); }
الشيفرة 8: محاولة استخدام مغلف يطبق السمة FnOnce
فقط مع التابع sort_by_key
يمثّل ما سبق طريقةً معقدة (لا تعمل بنجاح) لمحاولة عدّ المرات التي يُستدعى بها التابع sort_by_key
عند ترتيب list
، وتحاول الشيفرة البرمجية تحقيق ذلك بإضافة value
-ألا وهي قيمة من النوع String
من بيئة المغلف- إلى الشعاع sort_operations
. يحصل المغلف على القيمة value
، ثم ينقلها خارج المغلف بنقل ملكيتها إلى الشعاع sort_operations
، ويمكن أن يُستدعى هذا المغلف مرةً واحدةً إلا أن محاولة استدعائه للمرة الثانية لن تعمل لأن value
لن يكون في البيئة ليُضاف إلى الشعاع sort_operations
مجددًا، وبالتالي يطبق هذا المغلف السمة FnOnce
فقط، وعندما نحاول تصريف الشيفرة البرمجية السابقة، سنحصل على خطأ مفاده أن value
لا يمكن نقلها خارج المغلف لأن المغلف يجب أن يطبّق السمة FnMut
:
$ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure --> src/main.rs:18:30 | 15 | let value = String::from("by key called"); | ----- captured outer variable 16 | 17 | list.sort_by_key(|r| { | --- captured by this `FnMut` closure 18 | sort_operations.push(value); | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait For more information about this error, try `rustc --explain E0507`. error: could not compile `rectangles` due to previous error
يشير الخطأ إلى السطر الذي ننقل فيه القيمة value
خارج البيئة داخل متن المغلف، ولتصحيح هذا الخطأ علينا تعديل متن المغلف بحيث لا ينقل القيم خارج البيئة. نحافظ على وجود عدّاد في البيئة ونزيد قيمته داخل المغلف بحيث نستطيع عدّ المرات التي يُستدعى فيها التابع sort_by_key
. يعمل المغلف في الشيفرة 9 مع sort_by_key
لأنه يحصل فقط على المرجع المتغيّر الخاص بالعداد num_sort_operations
ويمكن بالتالي استدعاؤه أكثر من مرة واحدة:
اسم الملف: src/main.rs
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let mut list = [ Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }, ]; let mut num_sort_operations = 0; list.sort_by_key(|r| { num_sort_operations += 1; r.width }); println!("{:#?}, sorted in {num_sort_operations} operations", list); }
الشيفرة 9: استخدام مغلف يطبق السمة FnMut
مع التابع sort_by_key
دون الحصول على أخطاء
سمات Fn
مهمة عند تعريف أو استخدام الدوال أو الأنواع التي تستخدم المغلفات. سنناقش في المقال التالي المكررات iterators، إذ أن العديد من توابع المكررات تأخذ المغلفات مثل وسطاء، لذا تذكّر التفاصيل المتعلقة بالمغلّفات عند قراءة المقالة التالية.
ترجمة -وبتصرف- لقسم من الفصل Functional Language Features: Iterators and Closures من كتاب The Rust Programming Language.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.