نستعرض في هذه المقالة كل من الأنواع 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.