كيف يحول Rust البايتات إلى نصوص: الغوص تحت الغطاء
تخيل أنك استقبلت بيانات عبر الشبكة، أو قرأت ملفًا، أو فككت تشفير رسالة. أمامك مجرد مجموعة من الأرقام: vec![72, 101, 108, 108, 111]. إنها "Hello"! لكن Rust لن يسمح لك ببساطة بالقول: "أجل، هذا نص". لماذا؟ لأن Rust مثالي. لا يثق في البايتات حتى يتأكد من أنها UTF-8 صحيح.
سؤال "How do I convert a Vector of bytes (u8) to a string?" هو أحد أكثر الأسئلة شيوعًا على Stack Overflow. وليس بدون سبب. في هذه المقالة، لن نعرض فقط الكود الجاهز، بل سننظر تحت الغطاء: كيف يتحقق Rust من البايتات، وما الحفر المخفية التي تنتظرك، ولماذا unsafe ليس دائمًا شرًا.
لماذا Rust صارم جدًا؟
في Rust، النص (String أو &str) ليس مجرد مصفوفة من الأحرف. إنه تسلسل UTF-8 صالح مضمون. على عكس C أو Python حيث النص هو بايتات خام، يقدم Rust عقدًا: إذا كان هذا نصًا، فهو صحيح. هذا يحميك من أخطاء الترميز التي تطارد المطورين لعقود.
متجه البايتات Vec<u8> هو مجرد مجموعة من الأرقام. لا يعرف Rust ما قصدته: نصًا أم صورة أم بروتوكولًا ثنائيًا. لذلك، التحويل البسيط (cast) محظور. تحتاج إلى تحويل صريح.
الطريقة 1: الآمنة — String::from_utf8()
هذه أداتك الرئيسية. تأخذ الدالة Vec<u8> وتعيد Result<String, FromUtf8Error>.
fn main() { let bytes = vec![72, 101, 108, 108, 111]; // "Hello" match String::from_utf8(bytes) { Ok(s) => println!("نجاح: {}", s), Err(e) => println!("خطأ: {}", e), }}كيف يعمل هذا تحت الغطاء؟
- يمر Rust عبر كل بايت ويتحقق مما إذا كان التسلسل يتوافق مع قواعد UTF-8.
- إذا كان كل شيء على ما يرام — يقوم "بإعادة تغليف" الذاكرة: يصبح متجه البايتات نصًا. لا نسخ للبيانات! هذا تجريد بدون تكلفة (zero-cost abstraction).
- إذا كان هناك خطأ — تحصل على
Errيحتوي على المتجه الأصلي. يمكنك تحليله أو تجربة طريقة أخرى.
متى تستخدم؟ دائمًا عندما لا تكون متأكدًا من مصدر البيانات: إدخال المستخدم، حزم الشبكة، الملفات.
الطريقة 2: المتسامحة — String::from_utf8_lossy()
ماذا تفعل إذا كانت البايتات معيبة جزئيًا؟ على سبيل المثال، تقرأ ملفًا نصيًا قديمًا تسلل إليه بايت تالف. from_utf8 سيعيد خطأ. from_utf8_lossy سيعيد نصًا، مستبدلاً التسلسلات غير الصالحة برمز الاستبدال � (U+FFFD).
fn main() { // 0xFF — بايت غير صالح في UTF-8 (إذا لم يكن جزءًا من تسلسل متعدد البايتات) let bytes = vec![72, 101, 108, 108, 111, 0xFF]; let s = String::from_utf8_lossy(&bytes); println!("{}", s); // سيطبع: Hello�}اختلاف مهم: هذه الدالة تأخذ &[u8] (مرجع إلى شريحة)، وليس Vec<u8>. لا تستهلك ملكية المتجه. النتيجة هي Cow<'_, str> (نسخ عند الكتابة). إذا كانت البايتات صالحة — فهي مجرد مرجع للبيانات الأصلية. إذا لم تكن — يتم إنشاء نص جديد مع الاستبدالات.
متى تستخدم؟ عند التسجيل، التصحيح، العمل مع بيانات "مزعجة" حيث فقدان بضعة أحرف غير حرج.
الطريقة 3: السريعة والخطيرة — String::from_utf8_unchecked()
هذه طريقة unsafe. تأخذ ببساطة متجه البايتات وتقول: "حسنًا، أنا أثق بك، هذا نص". لا توجد أي تحققات. إذا أخطأت — ستحصل على سلوك غير محدد (undefined behavior). قد ينهار البرنامج، أو يدخل في حلقة لا نهائية، أو الأسوأ من ذلك، يعمل بشكل غير صحيح.
fn main() {\