اذهب إلى المحتوى

الأنواع والدوال المتقدمة في لغة رست


Naser Dakhel

نستعرض في هذه المقالة كل من الأنواع types والدوال functions المتقدمة في لغة رست.

الأنواع المتقدمة

يحتوي نظام نوع رست على بعض الميزات التي ذكرناها سابقًا إلا أننا لم نناقشها بالتفصيل بعد، وسنبدأ بمناقشة الأنواع الجديدة newtypes بصورةٍ عامة والنظر إلى فائدتها كأنواع، ثم ننتقل إلى كتابة الاختصارات وهي ميزة مشابهة للأنواع الجديدة ولكن بدلالات مختلفة قليلًا. سنناقش أيضًا النمط ! والأنواع ذات الحجم الديناميكي dynamically sized types.

استخدام نمط النوع الجديد لأمان النوع والتجريد

ملاحظة: يفترض هذا القسم أنك قرأت قسم ''استخدام نمط النوع الجديد لتنفيذ سمات الخارجية على الأنواع الخارجية'' من المقال السابق مفاهيم متقدمة عن السمات Trait في لغة رست.

يُعد نمط النوع الجديد مفيدًا أيضًا للمهمات التي تتجاوز تلك التي ناقشناها حتى الآن بما في ذلك الفرض الصارم بعدم الخلط بين القيم وكذلك الإشارة إلى وحدات القيمة. رأيتَ مثالًا على استخدام أنواع جديدة للإشارة إلى الوحدات في الشيفرة 15 من المقال السابق، تذكر أن هياكل Millimeters و Meters تغلف قيم u32 في نوع جديد. إذا كتبنا دالة بمحدد من النوع Millimeters فلن نتمكن من تصريف برنامج حاولَ عن طريق الخطأ استدعاء هذه الدالة بقيمة من النوع Meters أو u32 عادي.

يمكننا أيضًا استخدام نمط النوع الجديد للتخلص من بعض تفاصيل التطبيق الخاصة بنوع ما، ويمكن أن يكشف النوع الجديد عن واجهة برمجية عامة API تختلف عن الواجهة البرمجية للنوع الداخلي الخاص.

يمكن أن تخفي الأنواع الجديدة أيضًا التطبيق الداخلي، إذ يمكننا على سبيل المثال يمكننا منح نوع People لتغليف <HashMap<i32, String الذي يخزن معرف الشخص المرتبط باسمه. تتفاعل الشيفرة التي تستخدم People فقط مع الواجهة البرمجية العامة التي نقدمها مثل تابع لإضافة سلسلة اسم إلى مجموعة People، ولن تحتاج هذه الشيفرة إلى معرفة أننا نعيِّن معرفًا i32 للأسماء داخليًا. يعد نمط النوع الجديد طريقةً خفيفةً لتحقيق التغليف لإخفاء تفاصيل التطبيق التي ناقشناها سابقًا في قسم "التغليف وإخفاءه لتفاصيل التنفيذ" من المقال البرمجة كائنية التوجه OOP في لغة رست.

إنشاء مرادفات للنوع بواسطة اسماء النوع البديلة

توفّر رست القدرة على التصريح عن اسم بديل للنوع type alias لمنح نوع موجود اسمًا آخر، ونستخدم لذلك الكلمة المفتاحية type. يمكننا على سبيل المثال منح الاسم البديل Kilometers للنوع i32 على النحو التالي:

 type Kilometers = i32;

يصبح الاسم المستعار Kilometers الآن مرادفًا للنوع i32 على عكس أنواع Millimeters و Meters التي أنشأناها في الشيفرة 15، إذ أن Kilometers ليست نوعًا جديدًا منفصلًا. ستُعامل القيم ذات النوع Kilometers نفس معاملة قيم النوع i32:

    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;

    println!("x + y = {}", x + y);

يمكننا إضافة قيم من كلا النوعين نظرًا لأن Kilometers و i32 من النوع ذاته، كما يمكننا تمرير قيم Kilometers إلى الدوال التي تأخذ معاملات i32. لن نحصل على مزايا التحقق من النوع التي نحصل عليها من نمط النوع الجديد الذي ناقشناه سابقًا إذا استخدمنا هذا التابع، أي بعبارة أخرى إذا خلطنا قيم Kilometers و i32 في مكان ما فلن يعطينا المصرف خطأ.

حالة الاستخدام الرئيسة لأسماء النوع البديلة هي تقليل التكرار، على سبيل المثال قد يكون لدينا نوع طويل مثل هذا:

Box<dyn Fn() + Send + 'static>

يمكن أن تكون كتابة هذا النوع المطول في بصمات الدوال ومثل تعليقات توضيحية للنوع في جميع أنحاء الشيفرة أمرًا مملًا وعرضةً للخطأ. تخيل وجود مشروع مليء بالسطر السابق كما توضح الشيفرة 24.

    let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));

    fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
        // --snip--
    }

    fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
        // --snip--
    }

[الشيفرة 24: استعمال نوع طويل في أماكن كثيرة]

يجعل الاسم البديل للنوع هذه الشيفرة أكثر قابلية للإدارة عن طريق تقليل التكرار، إذ قدّمنا في الشيفرة 25 اسمًا بديلًا هو Thunk للنوع المطول ويمكننا استبدال جميع استخدامات النوع بالاسم البديل الأقصر Thunk.

    type Thunk = Box<dyn Fn() + Send + 'static>;

    let f: Thunk = Box::new(|| println!("hi"));

    fn takes_long_type(f: Thunk) {
        // --snip--
    }

    fn returns_long_type() -> Thunk {
        // --snip--
    }

[الشيفرة 25: استخدام اسم بديل Thunk لتقليل التكرار]

هذه الشيفرة أسهل في القراءة والكتابة، ويمكن أن يساعد اختيار اسم ذي معنى لاسم بديل للنوع على إيصال نيتك أيضًا، إذ أن thunk هي كلمة لشيفرة تُقيَّم في وقت لاحق لذا فهو اسم مناسب للمغلّف closure الذي يُخزَّن.

تُستخدم الأسماء البديلة للنوع أيضًا كثيرًا مع نوع <Result<T, E لتقليل التكرار، خذ على سبيل المثال وحدة std::io في المكتبة القياسية، إذ غالبًا ما تُعيد عمليات الدخل والخرج النوع <Result<T, E للتعامل مع المواقف التي تفشل فيها العمليات هذه، وتحتوي هذه المكتبة على هيكل std::io::Error الذي يمثل جميع أخطاء الدخل والخرج المحتملة، وتعيد العديد من الدوال في std::io النوع<Result<T, E بحيث تكون قيمة E هي std::io::Error كما هو الأمر بالنسبة للدوال الموجودة في سمة Write:

use std::fmt;
use std::io::Error;

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
    fn flush(&mut self) -> Result<(), Error>;

    fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}

تكررت <Result<..., Error كثيرًا، كما احتوت الوحدة std::io على هذا النوع من التصريح:

type Result<T> = std::result::Result<T, std::io::Error>;

يمكننا استخدام الاسم البديل المؤهل كليًا <std::io::Result<T لأن هذا التصريح موجود في الوحدة std::io، ويعني النوع السابق وجود النوع <Result<T, E مع ملء E بقيمة std::io::Error. تبدو بصمة السمة Write بنهاية المطاف على النحو التالي:

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;

    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}

يساعد الاسم البديل للنوع بطريقتين، فهو يجعل كتابة الشيفرات أسهل ويعطينا واجهةً متسقة عبر جميع أنواع std::io، إذ نظرًا لأن الاسم البديل هو <Result<T, E ببساطة فهذا يعني أننا نستطيع استخدام أي تابع يعمل على <Result<T, E بالإضافة إلى صيغة خاصة مثل العامل ?.

النوع Never الذي لا يعيد أي قيمة

تمتلك لغة رست نوعًا خاصًا يدعى !، وهذا النوع معروف في لغة نظرية النوع بالنوع الفارغ empty type لأنه لا يحتوي على أي قيم، إلا أننا نفضل أن نطلق عليه اسم ''أبدًا Never'' لأنه يحلّ مكان النوع المُعاد عندما لا تُعيد الدالة أي قيمة، إليك مثالًا على ذلك:

fn bar() -> ! {
    // --snip--
}

تُقرأ الشيفرة السابقة على أنّ الدالة bar لا تُعيد أي قيمة، وتسمى الدوال التي لا تُعيد أي قيمة بالدوال المتباينة diverging functions. لا يمكننا إنشاء قيم من النوع ! لذلك لا يمكن للدالة bar أن تُعيد أي شيء.

لكن ما فائدة نوع لا يمكنك أبدًا إنشاء قيم له؟ تذكر الشيفرة 5 سابقًا من المقال لغة رست غير الآمنة Unsafe Rust التي كانت جزءًا من لعبة التخمين بالأرقام، ولنعيد إنتاج جزء منها هنا في الشيفرة 26.

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

[الشيفرة 26: بنية match بذراع ينتهي بتعليمة continue]

تخطينا بعض التفاصيل في هذه الشيفرة، إذ ناقشنا سابقًا في المقال بنية match للتحكم بسير برامج لغة رست، أن أذرع match يجب أن تُعيد جميعها النوع ذاته، وهذا السبب وراء عدم عمل الشيفرة التالية:

    let guess = match guess.trim().parse() {
        Ok(_) => 5,
        Err(_) => "hello",
    };

يجب أن يكون النوع guess في هذه الشيفرة عددًا صحيحًا وسلسلة وتتطلب رست أن يكون guess نوعًا واحدًا فقط. إذًا، ماذا تُعيد continue؟ كيف سُمحَ لنا بإعادة u32 من ذراع مع وجود ذراع آخر ينتهي بالتعليمة continue في الشيفرة 26؟

لربّما خمّنت ذلك فعلًا، إذ للتعليمة continue قيمة !، وذلك يعني أنه عندما تحسب رست النوع guess فإنها تنظر إلى ذراعي التطابق، الأول بقيمة u32 والأخير بقيمة !، ولأنه ليس من الممكن للقيمة ! أن تكون لها قيمة أبدًا، تقرر رست أن النوع guess هو u32.

الطريقة الرسمية لوصف هذا السلوك هي أنه يمكن إجبار التعبيرات من النوع ! على أي نوع آخر. يُسمح لنا بإنهاء ذراع match هذا بالكلمة المفتاحية continue لأن continue لا تُعيد قيمة، ولكن بدلًا من ذلك يُنقل عنصر التحكم مرةً أخرى إلى أعلى الحلقة، لذلك في حالة Err لا نعيّن قيمة للمتغير guess إطلاقًا.

النوع "أبدًا never" مفيدٌ في ماكرو !panic أيضًا؛ تذكر دالة unwrap التي نستدعيها على قيم <Option<T لإنتاج قيمة أو هلع بهذا التعريف:

impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
}

يحدث أمر مماثل في هذه الشيفرة للشيفرة 26 ضمن match، إذ ترى رست أن val لديه النوع T و !panic من النوع ! لذا فإن نتيجة تعبير match الكلي هي T. تعمل هذه الشيفرة لأن !panic لا تنتج قيمة تنهي البرنامج. لن نعيد قيمة من unwrap في حالة None لذا فإن هذه الشيفرة صالحة.

يحتوي تعبير أخير على النوع ! ألا وهو الحلقة:

    print!("forever ");

    loop {
        print!("and ever ");
    }

لا تنتهي هنا الحلقة أبدًا لذا فإن ! هي قيمة التعبير، ومع ذلك لن يكون هذا صحيحًا إذا ضمنّنا break لأن الحلقة ستنتهي عندما تصل إلى break.

الأنواع ذات الحجم الديناميكي والسمة Sized

تحتاج رست إلى معرفة تفاصيل معينة حول الأنواع المستخدمة، مثل مقدار المساحة المراد تخصيصها لقيمة من نوع معين، وهذا يجعل من أحد جوانب نظام النوع الخاص به مربكًا بعض الشيء في البداية، تحديدًا مفهوم الأنواع ذات الحجم الديناميكي Dynamically Sized Types، ويشار إليها أحيانًا باسم DST أو الأنواع غير محددة الحجم unsized types، إذ تتيح لنا هذه الأنواع كتابة الشيفرات باستخدام قيم لا يمكننا معرفة حجمها إلا وقت التنفيذ.

لنتعمق في تفاصيل النوع ذو الحجم الديناميكي المسمى str الذي استخدمناه سابقًا في جميع أنحاء السلسلة البرمجة بلغة رست، لاحظ أننا لم نقل str& وإنما str بذاتها، إذ تُعدّ من الأنواع ذات الحجم الديناميكي. لا يمكننا معرفة طول السلسلة حتى وقت التنفيذ، مما يعني أنه لا يمكننا إنشاء متغير من النوع str، ولا يمكننا أخذ وسيط من النوع str. ألقِ نظرةً على الشيفرة التالية التي لا تعمل:

    let s1: str = "Hello there!";
    let s2: str = "How's it going?";

تحتاج رست أن تعرف مقدار الذاكرة المراد تخصيصها لأي قيمة من نوع معين ويجب أن تُستخدم جميع قيم النوع نفس المقدار من الذاكرة. إذا سمحت لنا رست بكتابة هذه الشيفرة فستحتاج قيمتي str هاتين إلى شغل المقدار ذاته من المساحة، إلا أن للقيمتين أطوال مختلفة، إذ يحتاج s1 إلى 12 بايت من التخزين ويحتاج s2 إلى 15، ولهذا السبب لا يمكن إنشاء متغير يحمل نوعًا محدد الحجم ديناميكيًا.

إذًا ماذا نفعل؟ يجب أن تعلم الإجابة مسبقًا في هذه الحالة، إذ أن الحلّ هو بإنشاء الأنواع s1 و s2و str& بدلًا من str. تذكر سابقًا من قسم "شرائح السلاسل النصية" في المقال المراجع References والاستعارة Borrowing والشرائح Slices في لغة رست أن هيكل بيانات الشريحة يخزن فقط موضع البداية وطول الشريحة، لذلك على الرغم من أن T& هي قيمة واحدة تخزن عنوان الذاكرة الخاص بالمكان الذي يوجد فيه T إلا أن str& هي قيمتان، ألا وهما عنوان str وطولها، ويمكننا على هذا النحو معرفة حجم قيمة str& في وقت التصريف، وهي ضعف طول usize، أي أننا نعرف دائمًا حجم str& بغض النظر عن طول السلسلة التي تشير إليها. هذه هي الطريقة التي تُستخدم بها الأنواع ذات الحجم الديناميكي عمومًا في رست، إذ لهذه الأنواع مقدار إضافي من البيانات الوصفية metadata التي تخزن حجم المعلومات الديناميكية. القاعدة الذهبية للأنواع ذات الحجم الديناميكي هي أنه يجب علينا دائمًا وضع قيم للأنواع ذات الحجم الديناميكي خلف مؤشر من نوع ما.

يمكننا دمج str مع جميع أنواع المؤشرات، على سبيل المثال <Box<str أو <Rc<str، وقد فعلنا ذلك سابقًا ولكن بنوع ذو حجم ديناميكي مختلف، ألا وهو السمات traits، فكل سمة هي نوع ذو حجم ديناميكي يمكننا الرجوع إليه باستخدام اسم السمة. ذكرنا سابقًا في المقال استخدام كائنات السمة Object Trait في لغة رست أنه يجب وضع السمات خلف مؤشر لاستخدامها مثل كائنات سمات، مثل dyn Trait& أو <Box<dyn Trait (يمكن استخدام <Rc<dyn Trait أيضًا).

توفر رست سمة Sized للعمل مع الأنواع ذات الأحجام الديناميكية لتحديد ما إذا كان حجم النوع معروفًا أم لا في وقت التصريف، إذ تُطبق هذه السمة تلقائيًا لكل شيء يُعرف حجمه في وقت التصريف، كما تضيف رست ضمنيًا تقييدًا على Sized لكل دالة عامة. يُعامل تعريف دالة عامة مثل هذه:

fn generic<T>(t: T) {
    // --snip--
}

كما لو أننا كتبنا هذا:

fn generic<T: Sized>(t: T) {
    // --snip--
}

ستعمل الدوال العامة افتراضيًا فقط على الأنواع التي لها حجم معروف في وقت التصريف، ومع ذلك يمكنك استخدام الصيغة الخاصة التالية لتخفيف هذا التقييد:

fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

الصفة مرتبطة بـ Sized? تعني أن "T قد تكون أو لا تكون Sized" وهذا الترميز يلغي الافتراض الذي ينص على وجود حجم معروف للأنواع العامة وقت التصريف. صيغة Trait? بهذا المعنى متاحة فقط للسمة Sized وليس لأي سمات أخرى.

لاحظ أيضًا أننا بدّلنا نوع المعامل t من T إلى T&، نظرًا لأن النوع قد لا يكون Sized فنحن بحاجة إلى استخدامه خلف نوع من المؤشرات، وفي هذه الحالة اخترنا مرجعًا.

الدوال functions والمغلفات closures المتقدمة

حان الوقت للتحدث عن بعض الخصائص المتقدمة المتعلقة بالمغلّفات والدوال بما في ذلك مؤشرات الدوال والمغلفات الراجعة Returing Closures.

مؤشرات الدوال

تحدثنا سابقًا عن كيفية تمرير المغلفات للدوال، ويمكننا أيضًا تمرير الدوال العادية للدوال. تفيد هذه التقنية عندما نريد تمرير دالة عرّفناها مسبقًا بدلًا من تعريف مغلف جديد. تُجبَر الدوال بالنوع fn (بحرف f صغير) -لا تخلط بينه وبين مغلف السمة Fn- يسمى نوع fn مؤشر دالة function pointer، ويسمح لك تمرير الدوال بمؤشرات الدوال باستخدام الدوال مثل وسطاء لدوال أُخرى.

تشابه صياغة مؤشرات الدوال لتحديد معامل مثل مؤشر صياغتها في المغلفات كما تبين الشيفرة 27، إذ عرّفنا تابع add_one الذي يضيف واحد إلى معامله. تأخذ الدالة do_twice معاملين، هما مؤشر دالة لأي دالة تأخذ معامل i32 وتعيد النوع i32، وقيمة i32 واحدة. تستدعي دالة do_twice الدالة f مرتين وتمرر قيمة arg وتضيف نتيجتَي استدعاء الدالة معًا، بينما تستدعي الدالة main الدالة do_twice مع الوسيطين add_one و 5.

اسم الملف: src/main.rs

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

[الشيفرة 27: استخدام نوع fn لقبول مؤشر دالة مثل وسيط]

تطبع الشيفرة السابقة ما يلي:

The answer is: 12

حددنا أن المعامل f في do_twice هو fn الذي يأخذ معامل واحد من النوع i32 ويُعيد i32، ويمكن بعدها استدعاء f من داخل الدالة do_twice. يمكننا في main تمرير اسم الدالة add_one على أنه الوسيط الأول إلى do_twice.

على عكس المغلفات، فإن fn هو نوع وليس سمة، لذا نحدد fn مثل نوع معامل مباشرة بدلًا من تصريح معامل نوع معمم generic مع واحدة من سمات fn على أنه قيد سمة trait bound.

تطبّق مؤشرات الدالة سمات المغلفة الثلاثة (Fn و FnMut و FnOnce). يعني ذلك أنه بإمكانك دائمًا تمرير مؤشر الدالة مثل وسيط لدالة تتوقع مغلفًا. هذه هي الطريقة الأفضل لكتابة الدوال باستخدام النوع المعمم وواحد من مغلف السمات بحيث يمكن للدوال الأخرى قبول دوال أو مغلفات. هناك مثال واحد تستطيع فيه قبول fn فقط وليس المغلفات وهو عندما نتعامل مع شيفرة خارجية لا تحتوي على مغلفات. يمكن لدوال لغة البرمجة سي أن تقبل الدوال مثل وسطاء، لكن ليس لديها مغلفات.

لنأخذ مثالًا عن مكان استخدام مغلف معرّف ضمنيًا أو دالة مسماة، ولنتابع كيفية استخدام تابع map مقدم بسمة Iterator في المكتبة القياسية. يمكننا استخدام المغلف لاستخدام دالة map لتحويل شعاع أرقام إلى شعاع سلاسل نصية على النحو التالي:

  let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(|i| i.to_string()).collect();

أو يتسمية التابع مثل وسيط map بدلًا من المغلف على النحو التالي:

  let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(ToString::to_string).collect();

لاحظ أنه يجب استخدام الصيغة المؤهلة كليًاالتي تحدثنا عنها سابقًا في قسم "السمات المتقدمة" لأنه يوجد دوال متعددة جاهزة اسمها to_string. استخدمنا هنا الدالة to_string المعرّفة في سمة ToString التي تطبّقها المكتبة القياسية لأي نوع يطبّق Display.

تذكر سابقًا من القسم "قيم التعداد" في المقال التعدادات enums في لغة رست أن اسم كل متغاير variant في تعداد enum عرّفناه يصبح أيضًا دالة تهيئة. يمكننا استخدام دوال التهيئة هذه مثل مؤشرات دالة تطبّق مغلفات السمة، ما يعني أنه يمكننا تحديد دوال التهيئة مثل وسطاء للتوابع التي تقبل المغلفات على النحو التالي:

  enum Status {
        Value(u32),
        Stop,
    }

    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();

أنشأنا هنا نسخةً من Status::Value باستخدام كل قيمة من النوع u32 ضمن المجال الذي استُدعي إليه map باستخدام دالة التهيئة Status::Value. يفضّل بعض الناس هذه الطريقة وآخرون يفضلون استخدام المغلفات، النتيجة بعد التصريف مماثلة للطريقتين لذا استخدم الطريقة الأوضح بالنسبة لك.

إعادة المغلفات

تُمثل المغلفات بسمات، ما يعني أنه لا يمكن إعادة المغلفات مباشرةً، إذ يمكنك استخدام النوع الحقيقي الذي ينفذ السمة مثل قيمة معادة للدالة في معظم الحالات عندما تريد إعادة سمة بدلًا من ذلك، ولكن لا يمكنك فعل ذلك في المغلفات لأنها لا تحتوي نوعًا حقيقيًا يمكن إعادته. على سبيل المثال، يُمنع استخدام مؤشرات الدالة fn مثل نوع مُعاد.

تحاول الشيفرة التالية إعادة مغلف مباشرةً، ولكنها لن تُصرّف.

fn returns_closure() -> dyn Fn(i32) -> i32 {
    |x| x + 1
}

يكون خطأ المصرّف على النحو التالي:

$ cargo build
   Compiling functions-example v0.1.0 (file:///projects/functions-example)
error[E0746]: return type cannot have an unboxed trait object
 --> src/lib.rs:1:25
  |
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of type `[closure@src/lib.rs:2:5: 2:8]`, which implements `Fn(i32) -> i32`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~~~~~~~~~~~~~~~~

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

يشير الخطأ إلى سمة Sized مجددًا. لا تعرف رست ما هي المساحة اللازمة لتخزين المغلف، وقد عرفنا حل هذه المشكلة سابقًا، إذ يمكننا استخدام كائن سمة.

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

تُصرّف الشيفرة بصورةٍ اعتيادية هنا. راجع المقال استخدام كائنات السمة Object Trait في لغة رست. سنتحدث لاحقًا عن الماكرو.

ترجمة -وبتصرف- لقسم من الفصل Advanced Features من كتاب The Rust Programming Language.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...