الشريحة كمصفوفة في Rust: لماذا هي صعبة وكيف تفعلها بشكل صحيح
\n\nكل من كتب في Rust لأكثر من يومين، واجه موقفًا مزعجًا: لديك &[u8] (شريحة)، والدالة تحتاج [u8; 4] (مصفوفة). في C كنت ستأخذ مؤشرًا. في Python كنت ستقطع. في Rust... يبدأ الرقص بالدف. لكن لا تخف - وراء هذا التعقيد يكمن أمان عبقري.
في هذه المقالة سنشرح كيفية الحصول على شريحة كمصفوفة في Rust بكل الطرق الممكنة: من "سريع وقذر" إلى "صحيح وموثوق". ستتعرف على لماذا لا يسمح Rust ببساطة بتحويل الشريحة إلى مصفوفة، وكيف تتجاوز هذه "المشكلة" بأقصى أداء.
\n\nلماذا لا يسمح Rust بنسخ الشريحة إلى مصفوفة ببساطة؟
\n\nتخيل: تطلب بيتزا، ويأتونك بقطعة. تقول: "هذه جزء من البيتزا، اجعلها كاملة". هكذا مع الشريحة - إنها مرجع لجزء من البيانات، بينما المصفوفة هي نوع مالك بطول ثابت. حجم المصفوفة معروف في وقت الترجمة، بينما حجم الشريحة معروف فقط في وقت التشغيل.
\n\nلذلك لا يمكنك ببساطة كتابة let arr: [u8; 4] = slice. سيقول المترجم: "آسف، لا أعرف إذا كانت شريحتك ستتناسب مع مصفوفة من 4 عناصر". Rust يضع الأمان قبل كل شيء - إذا كانت الشريحة أقصر، ستحصل على بيانات غير مفيدة أو ذعر.
الطريقة 1: try_into() - طريقة حديثة وآمنة
\n\nبدءًا من Rust 1.47، ظهرت طريقة أنيقة عبر trait TryInto. هذه الطريقة الموصى بها لتحويل الشريحة إلى مصفوفة. تُرجع Result، لذا تعرف دائمًا ما إذا كان التحويل ناجحًا.
use std::convert::TryInto;\n\nfn main() {\n let slice: &[u8] = &[10, 20, 30, 40, 50];\n \n // نحاول الحصول على مصفوفة من 3 عناصر\n let result: Result<[u8; 3], _> = slice[..3].try_into();\n \n match result {\n Ok(arr) => println!("المصفوفة: {:?}", arr),\n Err(_) => println!("عدد غير كافٍ من العناصر!"),\n }\n \n // أو ببساطة استخدم unwrap إذا كنت متأكدًا\n let arr: [u8; 4] = slice[..4].try_into().unwrap();\n println!("مصفوفة من 4: {:?}", arr);\n}\n\nكيف يعمل هذا؟ طريقة try_into() تتحقق من طول الشريحة في وقت التشغيل. إذا تطابق الطول - تحول، وإلا تُرجع خطأ. هذا حل مثالي لـ 99% من الحالات.
الطريقة 2: copy_from_slice() - لمن يحبون التحكم
\n\nإذا كان لديك بالفعل مصفوفة (مثلًا، مهيأة بالأصفار)، يمكنك نسخ البيانات من الشريحة. هذه الطريقة تتطلب ألا تكون الشريحة أقصر من المصفوفة - وإلا سيحدث ذعر.
\n\nfn main() {\n let slice: &[u32] = &[100, 200, 300];\n let mut arr: [u32; 3] = [0; 3]; // ننشئ مصفوفة فارغة\n \n arr.copy_from_slice(slice);\n println!("تم النسخ: {:?}", arr);\n \n // إذا كانت الشريحة أطول - خذ مجموعة فرعية\n let long_slice = &[1, 2, 3, 4, 5];\n let mut small_arr = [0u8; 2];\n small_arr.copy_from_slice(&long_slice[..2]);\n println!("جزء من الشريحة: {:?}", small_arr);\n}\n\nمهم: copy_from_slice يعمل فقط للأنواع التي تطبق Copy (مثل الأرقام). للسلاسل النصية يجب استخدام clone_from_slice.
الطريقة 3: التكرار اليدوي - للمغامرين
\n\nأحيانًا تحتاج لفعل شيء غير تقليدي. مثلًا، تحويل شريحة مع تغيير الترتيب أو التصفية. عندها يمكنك التكرار يدويًا:
\n\nfn slice_to_array<T: Copy, const N: usize>(slice: &[T]) -> Option<[T; N]> {\n if slice.len() != N {\n return None;\n }\n \n let mut arr = [unsafe { std::mem::zeroed() }; N];\n for (i, &val) in slice.iter().enumerate() {\n arr[i] = val;\n }\n Some(arr)\n}\n\nfn main() {\n