الدرس2 / 3
أنواع البيانات
قراءة
اللي هتفهمه
- الأنواع الأساسية وأحجامها الفعلية بالبايت، وإزاي تختار بينهم.
- الفرق بين الـ tuple والـ array، وليه حجم الاتنين ثابت.
- إيه اللي بيحصل لما الرقم يفيض، وليه التصرف بيختلف بين debug و release.
الشرح
Rust لغة ساكنة الأنواع: كل قيمة نوعها معروف وقت الترجمة. بس إنت مش مضطر تكتبه كل مرة — الكومبايلر بيستنتجه من السياق:
let x = 5; // i32 — الافتراضي
let y = 5.0; // f64 — الافتراضي
let z: u8 = 5; // لما تحدد، رأيك هو اللي بيتنفذالأنواع الأساسية أربع مجموعات: الأعداد الصحيحة، الأعداد العشرية، القيم المنطقية، والحروف.
الأعداد الصحيحة
الاسم بيتقري بسهولة: حرف بيقول هل بتقبل السالب، ورقم بيقول كام bit.
iمعناها signed — بتقبل السالب.uمعناها unsigned — من صفر وطالع.- الرقم هو عدد الـ bits: 8، 16، 32، 64، 128.
فيه كمان isize وusize، وحجمهم بحجم عنوان الذاكرة على الجهاز — 8 بايت على أي
جهاز 64-bit. usize هو نوع أي طول أو فهرس في اللغة، عشان كده هتشوفه كتير.
لو مش متأكد، استخدم i32. هو الافتراضي وهو أسرع نوع على أغلب الأجهزة.
المرئي
u8 كان هيبقى أقصر منu64 بـ 2 أُس 56 مرة، ومكنتش هتشوف حاجة أصلاً. الملوّن الغامق للأنواع اللي بتقبل السالب.الكود
جولة على الأنواع
fn main() {
let int: i32 = -42;
let uint: u8 = 200;
let float: f64 = 3.5;
let boolean: bool = true;
let letter: char = 'ح';
println!("{int} {uint} {float} {boolean} {letter}");
println!("char size = {} bytes", std::mem::size_of::<char>());
}افتح في Playgroundالخرج:
-42 200 3.5 true ح
char size = 4 byteschar في Rust 4 بايت، مش بايت واحد. هو بيمثّل نقطة Unicode كاملة، فالحرف
العربي والصيني والإيموجي كلهم بيتخزنوا فيه عادي. اكتبه بعلامة مفردة 'ح'؛
العلامة المزدوجة "ح" دي نص، حاجة تانية خالص.
الـ tuple والـ array
الاتنين بيجمّعوا قيم في حاجة واحدة، والفرق بينهم بسيط:
fn main() {
let tup: (i32, f64, char) = (500, 6.4, 'م');
let (a, b, c) = tup;
println!("{a} {b} {c}");
println!("first = {}, third = {}", tup.0, tup.2);
let arr = [1, 2, 3, 4, 5];
let months = ["يناير", "فبراير", "مارس"];
let zeros = [0; 5];
println!("{:?} len={}", arr, arr.len());
println!("{} {:?}", months[0], zeros);
}افتح في Playgroundالخرج:
500 6.4 م
first = 500, third = م
[1, 2, 3, 4, 5] len=5
يناير [0, 0, 0, 0, 0]- الـ tuple بيجمّع أنواع مختلفة. بتوصل لعناصره بالرقم:
tup.0. والتفكيكlet (a, b, c) = tup;بيديك أسماء للعناصر. - الـ array بيجمّع قيم من نفس النوع، وعدده جزء من نوعه:
[i32; 5]نوع مختلف عن[i32; 6]. [0; 5]اختصار لـ “خمس أصفار”.
الاتنين حجمهم ثابت ومعروف وقت الترجمة، فالاتنين بيقعدوا على الستاك. لو محتاج
حاجة تكبر وقت التشغيل، هتحتاج Vec — وده موضوع تاني.
() — الوحدة
let unit = ();النوع ده قيمته الوحيدة هي “ولا حاجة”، وحجمه صفر بايت. هيبان معناه في الدرس
الجاي: الدالة اللي مش بترجع حاجة، بترجع () فعلياً.
ليه بيشتغل كده
الفيضان — والفرق بين debug و release
u8 مداه من 0 لـ 255. طيب لو جمعت 250 و10؟
لو الكومبايلر قادر يحسب الناتج وهو بيترجم، هو بيرفض من أساسه ومش بيسيبك تشغّل:
error: this arithmetic operation will overflow
--> src/main.rs:4:13
|
4 | let c = a + b;
| ^^^^^ attempt to compute `250_u8 + 10_u8`, which would overflow
|
= note: `#[deny(arithmetic_overflow)]` on by defaultrustc 1.97.1 (8bab26f4f 2026-07-14)
بس القيم في برنامج حقيقي بتيجي من المستخدم أو من ملف، والكومبايلر مش شايفها. في الحالة دي التصرف بيختلف حسب نوع البناء:
في cargo run (بناء debug)، البرنامج بيقف فوراً:
thread 'main' (1703848) panicked at src/main.rs:2:5:
attempt to add with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtracerustc 1.97.1 (8bab26f4f 2026-07-14)
وفي cargo run --release، نفس الكود بيطبع 4.
الرقم لفّ حوالين المدى: 250 + 10 = 260، و260 − 256 = 4.
الاختلاف ده مقصود ومش عشوائي. فحص الفيضان بيكلّف وقت في كل عملية حسابية، وده غير مقبول في الكود المُحسَّن. فـ Rust بتخلّيه شغال في التطوير — حيث بتلاقي الأخطاء — وبتقفله في الإنتاج.
النتيجة العملية: متعتمدش على السلوك ده أبداً. لو عندك احتمال فيضان حقيقي، قول للكومبايلر إنت عايز إيه:
fn main() {
let a: u8 = 250;
let b: u8 = 10;
println!("wrapping = {}", a.wrapping_add(b));
println!("saturating = {}", a.saturating_add(b));
println!("checked = {:?}", a.checked_add(b));
println!("overflowing = {:?}", a.overflowing_add(b));
println!("checked ok = {:?}", a.checked_add(5));
}افتح في Playgroundالخرج:
wrapping = 4
saturating = 255
checked = None
overflowing = (4, true)
checked ok = Some(255)wrapping_addبتلف حوالين المدى، بنفس المنطق اللي بيحصل في release.saturating_addبتقف عند الحد الأقصى.checked_addبترجعNoneلو حصل فيضان، وSome(...)لو تمام. دي الأكتر أماناً — الكومبايلر هيجبرك تتعامل مع الحالتين.overflowing_addبترجع الناتج الملفوف ومعاهtrueلو حصل فيضان.
الأربعة دول تصرفهم واحد في debug و release. اللي بيختلف هو الجمع العادي + بس.
والفهرس؟
نفس الفكرة، بس هنا الفحص شغال في الاتنين:
error: this operation will panic at runtime
--> src/main.rs:4:20
|
4 | println!("{}", a[index]);
| ^^^^^^^^ index out of bounds: the length is 5 but the index is 10
|
= note: `#[deny(unconditional_panic)]` on by defaultrustc 1.97.1 (8bab26f4f 2026-07-14)
الفرق إن قراءة برّه حدود المصفوفة مش بتدّي رقم غلط — بتدّي وصول لذاكرة مش بتاعتك، وده ثغرة أمنية مش مجرد نتيجة خاطئة. عشان كده الفحص ده بيفضل شغال في release كمان.
تمرين
اتوقع الخرج، وبعدين شغّل واتأكد:
fn main() {
let a: i8 = 127;
println!("1: {}", a.wrapping_add(1));
println!("2: {}", a.saturating_add(1));
println!("3: {:?}", a.checked_add(1));
let t = (1, 2.5, 'x');
println!("4: {}", t.1);
let arr = [10; 3];
println!("5: {}", arr.len());
}الحل
1: -128
2: 127
3: None
4: 2.5
5: 31. i8 مداه من -128 لـ 127. الزيادة بتلف من أقصى اليمين لأقصى الشمال —
ودي أشهر مصيدة في الأنواع اللي بتقبل السالب: الزيادة بواحد طلّعت أصغر رقم ممكن.
2. saturating بتقف عند 127 ومش بتعدّيه.
3. checked بتقولك إن العملية مستحيلة بدل ما تديك رقم غلط.
4. t.1 هو العنصر التاني في الـ tuple — الترقيم بيبدأ من صفر.
5. [10; 3] معناها “تلات نسخ من الرقم 10”، مش “من 10 لـ 3”. الطول 3.
النقاش
لو عندك سؤال أو لقيت غلطة في الصفحة دي، اكتبها فيالنقاشات على GitHub. كل صفحة ليها موضوعها الخاص.