تخزين النصوص بترميز UTF-8 داخل السلاسل النصية في لغة رست Rust
تُعتبر لغة رست (Rust) من اللغات الحديثة التي اكتسبت شهرة واسعة خلال السنوات الأخيرة بفضل تركيزها القوي على الأمان، الأداء، والتحكم الدقيق بالذاكرة دون الحاجة إلى جامع نفايات (Garbage Collector). ومن أبرز المميزات التي تُظهر قوة هذه اللغة هي طريقة تعاملها مع النصوص (Strings)، وخصوصًا مع ترميز UTF-8، والذي يُعد من أكثر الترميزات استخدامًا وانتشارًا على مستوى العالم، خاصة في بيئة الويب والأنظمة متعددة اللغات.
يتطلب التعامل مع النصوص في البرامج الحديثة دعمًا كاملًا لتعدد اللغات، مما يعني بالضرورة دعم ترميز قادر على تمثيل معظم المحارف (characters) المعروفة في لغات البشر. ومن هنا يأتي الدور المحوري لترميز UTF-8، وقد اختارت لغة رست أن تعتمد هذا الترميز بشكل افتراضي عند تخزين النصوص داخل الكائنات النصية String و &str.
الأساس النظري لترميز UTF-8
UTF-8 (Unicode Transformation Format – 8 bit) هو أحد أنماط ترميز Unicode، ويتميز بكونه ترميزًا متغير الطول (Variable-length encoding). إذ يمكن أن يُمثل المحرف الواحد باستخدام 1 إلى 4 بايتات، حسب قيمة الكود النقطي (Code Point) لذلك المحرف.
| نطاق الكود النقطي | عدد البايتات في UTF-8 | البنية الثنائية العامة |
|---|---|---|
| U+0000 إلى U+007F | 1 بايت | 0xxxxxxx |
| U+0080 إلى U+07FF | 2 بايتات | 110xxxxx 10xxxxxx |
| U+0800 إلى U+FFFF | 3 بايتات | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 إلى U+10FFFF | 4 بايتات | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
ميزة UTF-8 الأهم هي التوافق العكسي مع ASCII (المستخدم في الترميز التقليدي للغة الإنجليزية)، والقدرة على تخزين كافة المحارف من أي لغة دون فقدان في المعلومات.
السلاسل النصية في لغة رست
في رست، هناك نوعان أساسيان للتعامل مع النصوص:
-
String: سلسلة نصية مملوكة (heap-allocated, growable). -
&str: سلسلة نصية غير مملوكة (عادةً تُعرف بالسلاسل الثابتة)، وتُعرف أيضًا بالمقطع النصي أو الشريحة النصية (string slice).
النوع String
هو النوع الذي يُستخدم عادة لتخزين النصوص القابلة للتعديل أثناء وقت التنفيذ. عند إنشاء كائن String جديد، فإن البيانات تُخزن في الذاكرة الديناميكية (Heap)، ويتم التعامل معها تلقائيًا من خلال واجهات أمان الذاكرة الخاصة بلغة رست.
rustlet mut my_string = String::from("مرحبًا بالعالم");
my_string.push_str(" — بلغة رست");
في هذا المثال، يتم تخزين سلسلة UTF-8 تحتوي على محارف عربية. جميع المحارف، حتى غير اللاتينية، تُخزن داخليًا بترميز UTF-8.
النوع &str
هو نوع غير مملوك، ويُستخدم للوصول إلى سلاسل نصية ثابتة أو مقاطع من سلسلة أكبر. يمكن أن يأتي من سلسلة String أو من نص ثابت مثل:
rustlet greeting: &str = "أهلاً بك";
هذه السلسلة النصية أيضًا يتم تخزينها بترميز UTF-8، كما هو الحال مع String.
لماذا تعتمد رست على UTF-8؟
الاختيار الصريح لترميز UTF-8 في رست لم يأتِ من فراغ، بل هو قرار تصميمي مدروس بناءً على عدة عوامل:
-
التوافق العالمي: UTF-8 هو الترميز القياسي في الويب (HTML, JSON, XML) والعديد من واجهات API.
-
الكفاءة في التخزين: محارف ASCII يتم تخزينها باستخدام بايت واحد، مما يجعل UTF-8 أكثر كفاءة مقارنةً بـ UTF-16 أو UTF-32 في الحالات التي يغلب فيها النص الإنجليزي.
-
التوافق العكسي مع ASCII: البرامج القديمة التي تعتمد على ASCII يمكن أن تتعامل مع سلاسل UTF-8 بشكل جزئي دون تعطل.
-
سهولة المعالجة: بفضل تصميم UTF-8، من السهل اكتشاف بداية ونهاية المحارف بدون الحاجة لمعالجة المصفوفة كلها، وهو أمر جوهري في بيئات البرمجة منخفضة المستوى.
التعامل مع المحارف والسلاسل في UTF-8
عند التعامل مع سلاسل نصية في رست، يجب دائمًا الانتباه إلى أن عدد البايتات لا يساوي عدد المحارف (characters) بسبب الترميز المتغير الطول. على سبيل المثال:
rustlet s = "سلام";
println!("Length in bytes: {}", s.len()); // الطول بالبايتات
println!("Number of characters: {}", s.chars().count()); // عدد المحارف
في هذا المثال، كلمة “سلام” تحتوي على 4 محارف ولكنها قد تستهلك أكثر من 4 بايتات (كل محرف عربي قد يحتاج 2-3 بايتات).
التجزئة الآمنة (Slicing) للنصوص
بسبب اختلاف الطول بين المحرف والبايت، فإن التجزئة في النصوص تتطلب انتباهاً خاصاً:
rustlet s = "مرحبا";
let slice = &s[0..2]; // خطأ محتمل: قد لا يطابق الحد بايتات UTF-8
لذلك، يجب استخدام طرق تعتمد على المحارف مثل:
rustlet first_char = s.chars().next().unwrap();
قراءة وتخزين البيانات النصية بترميز UTF-8
لغة رست تفترض أن جميع الملفات النصية (.rs, .toml, .txt) تُكتب بترميز UTF-8، ويقوم المترجم بالتحقق من صحة هذا الترميز. عند تحميل ملفات نصية أو قراءتها من مصادر خارجية، تُستخدم المكتبة القياسية أو مكتبات خارجية مثل serde للتعامل مع التشفير الصحيح.
مثال لقراءة ملف نصي بترميز UTF-8:
rustuse std::fs;
use std::io;
fn main() -> io::Result<()> {
let content = fs::read_to_string("data.txt")?;
println!("{}", content);
Ok(())
}
هذا الكود يُفترض ضمنًا أن الملف data.txt مُرمز بـ UTF-8. أي ترميز آخر سيؤدي إلى خطأ أثناء القراءة.
إنشاء سلاسل نصية متعددة اللغات
يمكن استخدام تراكيب String و format! لإنشاء نصوص تحتوي على لغات متعددة بسهولة:
rustlet name = "محمد";
let greeting = format!("مرحبًا، {}! أهلاً بك في Rust.", name);
println!("{}", greeting);
سيقوم الكائن String تلقائيًا بتخزين الناتج بترميز UTF-8 دون الحاجة إلى أي معالجة إضافية من طرف المبرمج.
إدارة الأخطاء المرتبطة بـ UTF-8
عند التعامل مع البيانات غير الموثوقة، مثل قراءة الملفات أو استقبال البيانات من الشبكة، قد تواجه بيانات لا تُطابق ترميز UTF-8. لذلك توفر رست واجهات للتعامل مع هذه الأخطاء:
rustuse std::str;
let bytes = vec![0xf0, 0x28, 0x8c, 0x28]; // بيانات غير صالحة
match str::from_utf8(&bytes) {
Ok(s) => println!("تمت القراءة: {}", s),
Err(e) => println!("خطأ في الترميز: {}", e),
}
يوفر هذا الأسلوب أمانًا من الأعطال والاختراقات المرتبطة بسوء تفسير البيانات الثنائية كنصوص.
الفرق بين char و u8 في رست
من الأمور الجوهرية في رست فهم الفرق بين char و u8:
-
char: يمثل محرف Unicode واحدًا (بما في ذلك الإيموجي والمحارف المعقدة)، ويُخزن داخليًا كـ 4 بايت. -
u8: يمثل بايت واحد فقط، ويُستخدم لتمثيل كل بايت من UTF-8 عند القراءة أو التحويل.
rustlet ch: char = 'ش';
let byte_length = ch.len_utf8(); // عدد البايتات اللازمة لتمثيل هذا المحرف في UTF-8
التوافق مع مكتبات أخرى
تعتمد العديد من المكتبات في منظومة Rust على تمثيل UTF-8، مثل:
-
serdeللتسلسل/إزالة التسلسل. -
regexللبحث في النصوص باستخدام التعابير النمطية. -
hyperوreqwestللتعامل مع بروتوكول HTTP. -
tokioوasync-stdللبرمجة غير المتزامنة مع النصوص.
جميعها تتعامل مع String أو &str باعتبارها UTF-8 بشكل افتراضي.
استخدام الجداول في تحليل النصوص بـ UTF-8
جدول: مقارنة بين خصائص أنواع السلاسل النصية في رست
| الخاصية | String |
&str |
|---|---|---|
| النوع | مملوك (heap-allocated) | غير مملوك (slice) |
| إمكانية التعديل | نعم | لا |
| التمثيل الداخلي | UTF-8 | UTF-8 |
| الاستهلاك في الذاكرة | أكبر لأنّه قابل للتعديل | أقل لأنه ثابت |
| الاستخدام النموذجي | النصوص المتغيرة | النصوص الثابتة أو المقاطع |
الخاتمة التقنية
تعتمد لغة رست بشكل صارم على UTF-8 في تمثيل جميع أنواع النصوص، مما يجعلها من أكثر اللغات حداثة وانفتاحًا على تعدد اللغات والثقافات. هذه البنية الصارمة والمبنية على مبدأ الأمان تُسهم في تقليل الأخطاء البرمجية المرتبطة بالترميز بشكل كبير، وتُوفر تجربة تطوير موثوقة عند بناء أنظمة متعددة اللغات أو تعتمد على معالجات نصوص معقدة.
يمثل دعم UTF-8 جزءًا لا يتجزأ من فلسفة رست: الأمان أولًا، مع الحفاظ على الأداء الفائق. لذا فإن فهم البنية الداخلية للنصوص، والتحكم في أنواع البيانات النصية، وطريقة تخزينها داخل الذاكرة هو مفتاح أساسي لكتابة تطبيقات رست قوية وفعّالة.
المراجع:
-
The Rust Programming Language, by Steve Klabnik and Carol Nichols
-
Unicode Standard Documentation, Unicode Consortium

