الدرس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. هو الافتراضي وهو أسرع نوع على أغلب الأجهزة.

المرئي

typebitsminmaxi88-128127u880255i1616-3276832767u1616065535i3232-21474836482147483647u323204294967295i6464-92233720368547758089223372036854775807u6464018446744073709551615
الأعمدة بمقياس لوغاريتمي — طول العمود بيتبع عدد الـ bits، مش المدى نفسه. لو كانت بمقياس خطي، عمود 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 bytes

char في 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 default

rustc 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 backtrace

rustc 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 default

rustc 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: 3

1. i8 مداه من ‎-128 لـ 127. الزيادة بتلف من أقصى اليمين لأقصى الشمال — ودي أشهر مصيدة في الأنواع اللي بتقبل السالب: الزيادة بواحد طلّعت أصغر رقم ممكن.

2. saturating بتقف عند 127 ومش بتعدّيه.

3. checked بتقولك إن العملية مستحيلة بدل ما تديك رقم غلط.

4. t.1 هو العنصر التاني في الـ tuple — الترقيم بيبدأ من صفر.

5. [10; 3] معناها “تلات نسخ من الرقم 10”، مش “من 10 لـ 3”. الطول 3.

النقاش

لو عندك سؤال أو لقيت غلطة في الصفحة دي، اكتبها فيالنقاشات على GitHub. كل صفحة ليها موضوعها الخاص.