من هم بحق الجحيم Fn وFnMut وFnOnce؟ إغلاقات Rust تحت المجهر

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

الإغلاقات في Rust: سحر أم رياضيات صارمة؟

\n\n

إذا كنت قد كتبت في Rust شيئاً مثل |x| x + 1، فقد استخدمت الإغلاقات. ولكن بمجرد أن تحاول تمرير هذا الإغلاق إلى دالة أو تخزينه في هيكل، يبدأ المترجم فجأة في المطالبة: «أي trait؟ Fn أم FnMut أم FnOnce؟».

\n\n

وهنا يقع الكثيرون في حيرة. خاصة إذا كنت قادماً من JavaScript أو Python أو Ruby، حيث الإغلاقات هي «مجرد دوال تلتقط المتغيرات». في Rust الأمر أكثر تعقيداً (وأكثر إثارة للاهتمام).

\n\n

اليوم سنفهم ما يحدث بالفعل تحت الغطاء، عندما يقرر Rust أي trait سيعطي لإغلاقك. هذا ليس سحراً – إنه نظام أنواع صارم يحميك من سباقات البيانات والمؤشرات المعلقة. هيا بنا!

\n\n

باختصار عن المهم: traits الإغلاقات الثلاثة

\n\n

في Rust هناك ثلاثة traits تنفذها الإغلاقات:

\n\n
    \n
  • FnOnce – يمكن استدعاؤه مرة واحدة. يأخذ ملكية المتغيرات الملتقطة.
  • \n
  • FnMut – يمكن استدعاؤه عدة مرات، لكن يمكنه تعديل المتغيرات الملتقطة (يأخذ &mut).
  • \n
  • Fn – يمكن استدعاؤه عدة مرات ولا يعدل المتغيرات الملتقطة (يأخذ &).
  • \n
\n\n

كل إغلاق ينفذ تلقائياً جميع traits المناسبة. أي إذا كان الإغلاق ينفذ Fn، فإنه ينفذ تلقائياً FnMut و FnOnce. لكن العكس لا يعمل.

\n\n

لماذا؟ لأن Fn هو الأكثر صرامة، و FnOnce هو الأضعف. هرمية الوراثة تبدو هكذا:

\n\n
FnOnce  ←  FnMut  ←  Fn
\n\n

أي أن FnMut: FnOnce، و Fn: FnMut. احفظ هذا السلم – إنه أساسي.

\n\n

كيف يقرر Rust أي trait يعطي للإغلاق؟

\n\n

ينظر المترجم إلى كيفية استخدام الإغلاق للمتغيرات الملتقطة. هناك ثلاثة أوضاع للالتقاط:

\n\n
    \n
  • بالمرجع (&T) – الإغلاق يقرأ المتغير فقط.
  • \n
  • بالمرجع القابل للتعديل (&mut T) – الإغلاق يعدل المتغير.
  • \n
  • بالقيمة (T) – الإغلاق يأخذ ملكية المتغير.
  • \n
\n\n

إليك كيف يؤثر ذلك على traits:

\n\n\n \n \n \n \n
طريقة الالتقاطTraits المنفذةمثال
فقط &TFn + FnMut + FnOnce|| println!("{}", x)
&mut T (وليس T)FnMut + FnOnce|| x += 1
T (ملكية)فقط FnOnce|| drop(x) أو move || x
\n\n

دعنا نحلل كل حالة بكود حي.

\n\n

مثال 1: قراءة فقط → Fn

\n\n
let x = 42;\nlet read_closure = || println!("x = {}", x);\n\n// يمكن استدعاؤه أي عدد من المرات\nread_closure();\nread_closure();\n\n// وحتى تمريره إلى دالة تتطلب Fn\nfn call_fn<F: Fn()>(f: F) {\n    f();\n}\ncall_fn(read_closure);
\n\n

هنا الإغلاق يقرأ x فقط. لا يحتاج لتعديله أو أخذه. Rust يفهم: «حسناً، هذا آمن – نعطيه Fn».

\n\n

مثال 2: تعديل → FnMut

\n\n
let mut x = 42;\nlet mut mut_closure = || {\n    x += 1;\n    println!("x = {}", x);\n};\n\nmut_closure(); // x = 43\nmut_closure(); // x = 44\n\n// نمرره إلى دالة تتطلب FnMut\nfn call_fn_mut<F: FnMut()>(mut f: F) {\n    f();\n    f();\n}\ncall_fn_mut(mut_closure);
\n\n

لاحظ: أعلنا mut_closure كـ let mut. لماذا؟ لأن استدعاء FnMut يتطلب قابلية للتعديل

المدونات

توصيات الكتب