مقالات وتدوينات
(0)

البرمجة بلغة رست

455 قراءة
1 تعليق
alt
التصنيف مقالات وتدوينات
وقت النشر
2022/12/04
الردود
1

أنواع البيانات في لغة رست 

تنتمي كل قيمة في لغة رست إلى نوع بيانات معيّن، ويُساعد ذلك لغة رست بمعرفة نوع البيانات التي تدلّ عليها هذه القيمة وكيفية التعامل معها، وسننظر إلى مجموعتين من أنواع البيانات، هي: القيم المُفردة scalar والقيم المركّبة compound.


تذكر أن لغة رست لغة برمجة متقيدة بأنواع البيانات statically typed، أي أنه يجب أن تعرف أنواع البيانات جميعها عند وقت التصريف، ويستطيع المصرّف عادةً استنتاج نوع المتغيرات بناءً على القيمة وكيفية استخدامها ضمن الشيفرة البرمجية، إلا أننا يجب أن نحدّد الأنواع في بعض الحالات، مثل التحويل من String إلى نوع عددي باستخدام parse


let guess: u32 = "42".parse().expect("Not a number!");

نحصل على الخطأ التالي إن لم نُضف النوع u32 : كما هو موضح أعلاه، ويدل الخطأ على أن المصرّف يحتاج المزيد من المعلومات حول النوع الذي نريد استخدامه:

$ cargo build

   Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)

error[E0282]: type annotations needed

 --> src/main.rs:2:9

  |

2 |     let guess = "42".parse().expect("Not a number!");

  |         ^^^^^ consider giving `guess` a type


For more information about this error, try `rustc --explain E0282`.

error: could not compile `no_type_annotations` due to previous error

ستجد ترميزًا مختلفًا لكل من أنواع البيانات الأخرى.


الأنواع المفردة

يمثّل النوع المفرد scalar type قيمة فردية، ولدى لغة رست أربع أنواع مُفردة أولية هيالأعداد الصحيحة integers والأعداد ذات الفاصلة العشرية floating-point numbers والقيم البوليانية booleans والمحارف characters، وقد تتعرف على بعضها من لغة برمجة أخرى تعاملت معها سابقًا




هنا سنتحدّث عن كيفية استعمال هذه الأنواع في لغة رست.

أنواع الأعداد الصحيحة

العدد الصحيح integer هو عدد لا يحتوي على جزء كسري، وسبق لنا استخدام نوع من أنواع الأعداد الصحيحة سابقًا وهو u32، ويحددالتصريح عن هذا النوع أن القيمة المُسندة إلى المتغير ستكون عدد صحيح عديم الإشارة unsigned integer (تبدأ الأعداد الصحيحةذات الإشارة بالحرف i بدلًا من u)، ويأخذ مساحة 32 بتيوضّح الجدول 3-1 أنواع الأعداد الصحيحة المُضمّنة في لغة رست، ويمكننااستخدام أي من هذه المتغايرات variants للتصريح عن نوع قيمة العدد الصحيح.

[جدول 3-1: أنواع الأعداد الصحيحة في راست]

يُمكن أن يكون كل متغاير ذو إشارة أو عديم إشارة وذو طول محدّد، إذ تُشير كلمة ذو إشارةsigned وعديم الإشارة unsigned إلى إمكانية كون العدد سالبًا أم لا، وبتعبير آخر، هليحتاج العدد إلى إشارةٍ معه (ذو إشارة signed) أم أنه سيكون موجبًا فقط وسيُمثّل بالتاليدون أي إشارة (عديم الإشارة unsigned). الأمر مماثل لكتابة الأعداد على ورقة، فعندمانحتاج لاستخدام الإشارة يُوضّح العدد وبجانبه إشارة (سواءً موجبة أو سالبة)، وفي حالكان الافتراض أن جميع الأعداد موجبة، فلا نضع أي إشارة بجانب الأعداد، وتُخزّن الأعدادذات الإشارة باستخدام تمثيل المتمّم الثنائي two's complement.

يُمكن أن يخزِّن كل متغاير ذي إشارة القيم المنتمية إلى المجال من 2n-1إلى 2n-1 - 1، إذ تمثّل"n" عدد البتات التي يستخدمها المتغاير، وبالتالي يمكن للنوع i8 تخزين القيم التي تنتميإلى المجال من 27إلى 1- 27 الذي يساوي من ‎-128 إلى 127، بينما يمكن للمتغايرات عديمةالإشارة تخزين القيم ضمن المجال من 0 إلى 2n-1، وبالتالي يمكن للنوع u8 أن يخزن الأعدادمن 0 إلى 28-1 وهو ما يساوي المجال من 0 إلى 255.

إضافةً لما سبق، يعتمد النوعان isize وusize على معمارية الحاسب الذي يعمل عليهبرنامجك، وهو بطول 64 بت إذا كان من معمارية 64 بت وبطول 32 بت إذا كان من معمارية32 بت.

يُمكنك كتابة الأعداد الصحيحة المُجرّدة integer literals بأي من التنسيقات الموضحة فيالجدول 3-2، لاحظ أن لغة رست توفر صياغة لكتابة الأعداد بطريقة تدل على نوعها لتمثيلعدة أنواع عددية إذ تسمح بوجود لاحقة للنوع type suffix مثل "57u8" لتحديد نوعه،ويُمكن أن تستخدم صياغة الأعداد تلك أيضًا الرمز _ بمثابة فاصل بصري لجعل الأعدادأسهل للقراءة مثل "1‎_000" الذي يحمل القيمة "1000" ذاتها.

العدد المجرد

مثال

عشري

98‎_222

ست عشري

0xff

ثُماني

0o77

ثُنائي

0b1111_0000

بايت (فقط بحجم u8)

b'A'‎

[جدول 3-2: الأعداد الصحيحة المجردة في لغة رست]

إذًا، كيف يمكنك معرفة أي أنواع الأعداد الصحيحة التي يجب عليك استخدامها؟ أنواع لغةرست الافتراضية هي الخيار الأمثل إذا لم تكُن متأكدًا بخصوص هذا الأمر، نوع العددالصحيح الافتراضي هو i32، والحالة التي قد تستخدم فيها أحد النوعين isize أوusize هي عندما تستخدم قيمة المتغير دليلًا index ما ضمن مجموعة collection.

طفحان الأعداد الصحيحة

بفرض أن هناك متغير من النوع u8 الذي يمكنه تخزين القيم من 0 إلى 255. إذا حاولتإسناد قيمة إلى ذلك المتغير خارج النطاق المذكور -مثل القيمة 256- فسيتسبب ذلك بحدوثما يسمى طفحان الأعداد الصحيحة integer overflow الذي قد يتسبب بحدوث نتيجة مناثنتان.

تتفقد لغة رست عند تصريف البرنامج في نمط تنقيح الأخطاء debug mode حالات طفحانالأعداد الصحيحة التي ستتسبب بهلع panic برنامجك عند تشغيله، ويستخدم مبرمجو لغةرست مصطلح هلع panic عندما يتوقف البرنامج بسبب خطأ ما، وسنناقش هذا الأمر بتعمقأكبر لاحقًالا تتحقق لغة رست من حالات طفحان الأعداد الصحيحة التي تتسبب بهلعالبرنامج عند تصريفه باستخدام نمط الإطلاق release mode باستخدام الراية flag‏ ‎--release، وتجري راست بدلًا من ذلك عمليةً تُعرف بانتقال المتمم الثنائي two's complement wrapping إذا حدث أي طفحانباختصار، تنتقل القيمة التي تحتوي علىقيمة أكبر من القيمة العظمى الممكن للنوع تخزينها إلى أصغر قيمة يمكن للمتغير تخزينها،فعلى سبيل المثال تصبح القيمة 256 في النوع u8 مساويةً إلى الصفر والقيمة 257 إلى 1 وهكذا، لن يهلع البرنامج في هذه الحالة، بل سيحمل المتغير قيمةً مختلفة، ويُعدّ الاعتمادعلى عملية الانتقال wrapping في طفحان الأعداد الصحيحة خطأً.

يُمكنك استخدام أحد الطرق التالية للتعامل على نحوٍ صريح مع حالات الطفحان وهي طرقمُضمنّة في المكتبة القياسية للأنواع العددية الأولية:

تمكين الانتقال في جميع أنماط بناء البرنامج باستخدام توابع wrapping_*‎ مثل wrapping_add.

إعادة القيمة None إذا لم يكن هناك أي طفحان باستخدام التوابع checked_*‎.

إعادة القيمة والقيمة البوليانية التي تشير إلى حدوث طفحان باستخدام توابع overflowing_*‎.

إشباع saturate القيم العُظمى والدُنيا للقيمة باستخدام توابع saturating_*‎.

أنواع أعداد الفاصلة العشرية

لدى لغة راست نوعَين من أنواع أعداد الفاصلة العشرية floating-point numbers وهيالأعداد التي تحتوي على فواصل عشرية، وهما f32 و f64، وبحجم 32 بت و64 بت،والنوع الافتراضي هو f64، لأنها تكون بنفس سرعة المُعالجات الحديثة f32 ولكنها أكثر دقة،وجميع أنواع أعداد الفاصلة العشرية ذات إشارة.

إليك مثالًا يوضح أعداد الفاصلة العشرية عمليًا:

اسم الملف: src/main.rs

fn main() {

    let x = 2.0; // f64


    let y: f32 = 3.0; // f32

}

تُمثّل أعداد الفاصلة العشرية بحسب معيار IEEE-754. للنوع f32 دقة وحيدة single-precision، بينما للنوع f64 دقة مضاعفة double precision.

العمليات على الأنواع العددية

تدعم لغة راست العمليات الرياضية الأساسية التي تتوقع إجرائها على الأنواع العددية،وهي الجمع والطرح والضرب والقسمة وباقي القسمةيُقرّب ناتج قسمة الأعداد الصحيحةإلى أقرب عدد صحيح، وتوضح الشيفرة البرمجية التالية كيفية إجراء كل من العملياتباستخدام تعليمة let:

اسم الملف src/main.rs

fn main() {

    // الجمع

    let sum = 5 + 10;


    // الطرح

    let difference = 95.5 - 4.3;


    // الضرب

    let product = 4 * 30;


    // القسمة

    let quotient = 56.7 / 32.2;

    let floored = 2 / 3; // Results in 0


    // باقي القسمة

    let remainder = 43 % 5;

}

يستخدم كل تعبير من التعابير السابقة عاملًا رياضيًا ويُقيّم الناتج إلى قيمة واحدة، ثمتُسند هذه القيمة إلى المتغير.

النوع البولياني

للنوع البولياني boolean type في لغة رست -كما هو الحال في معظم لغات البرمجةالأخرىقيمتان: true و false، ويبلغ حجم النوع هذا بتًا واحدًا، ويُحدّد النوع البوليانيفي لغة راست باستخدام الكلمة bool كما يوضح المثال التالي:

اسم الملف: src/main.rs

fn main() {

    let t = true;


    let f: bool = false; // تحديد النوع بوضوح

}

الاستخدام الأساسي للقيم البوليانية هو في التعابير الشرطية conditionals مثل تعابيرif، وسنغطّي تعابير if وكيفية عملها في لغة رست لاحقًا.

نوع المحرف

نوع char في لغة رست هو أكثر أنواع القيم الأبجدية بدائية، إليك بعض الأمثلة عن تصريحقيم char:

اسم الملف: src/main.rs

fn main() {

    let c = 'z'

    let z: char = 'ℤ' // تحديد النوع بوضوح

    let heart_eyed_cat = '?'

}

لاحظ أننا حددنا النوع char المجرد باستخدام علامتَي تنصيص فردية، بعكس نوع السلسلةالنصية string المجرّد الذي يستخدم علامتَي تنصيص مزدوجة، ويبلغ حجم النوع char فيلغة راست أربعة بايتات وتمثل القيمة قيمة يونيكود Unicode عددية التي يُمكن أن تمثلقيمًا أكثر ممّا تستطيع الآسكي ASCII تمثيلهتتضمن لغة راست كذلك الأحرف المُعلّمةaccented letters وكل من المحارف الصينية واليابانية والكورية، إضافةً إلى الرموزالتعبيرية emoji والمسافات الفارغة ذات العرض الصفري zero-width space، إذ تُعدجميع القيم السابقة المذكورة قيمًا صالحة ويُمكن تخزينها في متغير من نوع char.

تتراوح قيم يونيكود العددية من "U+0000" إلى "U+D7FF" ومن "U+E000" إلى"U+10FFFF"، إلا أن مصطلح المحرف character غير موجود في نظام اليونيكود،وبالتالي يمكن ألا يتطابق فهمك كإنسان لماهية المحرف مع تعريف النوع char في لغة راست،وسنناقش هذا الموضوع بالتفصيل لاحقًا.

الأنواع المركبة

يُمكن للأنواع المركبة compound types أن تجمع عدّة قيم في نوع واحد، وللغة رست نوعانمن الأنواع المركبة وهي المجموعات tuples والمصفوفات arrays.

نوع المجموعة

المجموعة هي طريقة عامة لجمع عدّة قيم من أنواع مختلفة إلى نوع مُركب واحد،وللمجموعات حجم مُحدّد إذ لا يُمكن أن يكبر أو يصغر الحجم بعد التصريح عنه.

نستطيع إنشاء مجموعة عن طريق كتابة لائحة من العناصر يُفصل ما بينها بالفاصلة داخلقوسين، وكل موضع داخل هذه اللائحة يمثل قيمةً بنوع مُعيّن، ويمكن أن تختلف هذه الأنواعفيما بينهاأضفنا أنواع عناصر اللائحة في مثالنا التالي ولكن هذه الخطوة اختيارية:

اسم الملف: src/main.rs

fn main() {

    let tup: (i32, f64, u8) = (500, 6.4, 1);

}

يُسند المتغير tup إلى كامل المجموعة، لأن المجموعة تمثّل عنصرًا مركبًا واحدًا، وللحصولعلى القيم الفردية داخل المجموعة يمكننا استخدام مطابقة الأنماط pattern matching لتفكيك destructure قيمة المجموعة كما هو موضح:

اسم الملف: src/main.rs

fn main() {

    let tup = (500, 6.4, 1);


    let (x, y, z) = tup;


    println!("The value of y is: {y}");

}

يُنشئ هذا البرنامج مجموعة ويُسندها إلى المتغير tup، ومن ثم يستخدم نمطًا مع let لأخذالمتغير tup وتحويله إلى ثلاث قيم منفصلة وهي x و y و z، ويدعى هذا بالتفكيكdestructuring لأنه يُفكك المجموعة الواحدة إلى ثلاث أجزاء، ويطبع البرنامج أخيرًا قيمة y المساوية إلى "6.4".

يمكننا أيضًا الوصول إلى عناصر المجموعة مباشرةً باستخدام النقطة (.) متبوعةً بدليلالقيمة التي نريد الوصول إليها، كما هو موضح في المثال التالي:

اسم الملف: src/main.rs

fn main() {

    let x: (i32, f64, u8) = (500, 6.4, 1);


    let five_hundred = x.0;


    let six_point_four = x.1;


    let one = x.2;

}

يُنشئ هذا البرنامج مجموعةً باسم x، ثم يستخدم قيمة كل من عناصرها باستخدام دليل كلمنها، ويبدأ الدليل الأول بالرقم 0 كما هو الحال في معظم لغات البرمجة.

للمجموعة اسم مميّز إذا كانت فارغة ألا وهو الوحدة unit، وتُكتب قيمتها وقيمة أنواعهابالشكل ()، اللتان تُمثّلان قيمة فارغة أو قيمة إعادة فارغة empty return type، تُعيدالتعابير ضمنيًا قيمة الوحدة إذا لم يكن التعبير يُعيد أي قيمة أخرى.

نوع المصفوفة

المصفوفة هي نوع من الأنواع الأخرى التي تحتوي على مجموعة من قيم متعددة، ويجب أنتكون جميع هذه القيم من النوع ذاته على عكس المجموعة، وللمصفوفات حجم ثابت بعكسبعض لغات البرمجة الأخرى.

نكتب القيم في المصفوفة مثل لائحة من القيم مفصول ما بينها بفاصلة داخل أقواس معقوفةsquare brackets:

اسم الملف: src/main.rs

fn main() {

    let a = [1, 2, 3, 4, 5];

}

يُمكن للمصفوفات أن تكون مفيدةً عندما تريد من بياناتك أن تكون موجودةً على المكدّسstack بدلًا من الكومة heap (سنناقش المكدس والكومة لاحقًاأو عندما تريد أن تتأكد أنهناك مجموعة ثابتة العدد من العناصرالمصفوفة ليست نوعًا مرنًا مثل نوع الشعاعvector، فالشعاع هو نوع مماثل يحتوي على مجموعة وهو مُضمّن في المكتبة القياسيةويمكن أن يتغير حجمه بالزيادة أو النقصان، وإن لم تكُن متأكدًا أيُّهما تستخدم، فذلك يعنيأنك غالبًا بحاجة استخدام الشعاع، وسنناقش هذا الأمر بالتفصيل لاحقًا.

تبرز أهمية المصفوفات عندما تعرف عدد العناصر التي تحتاجها، على سبيل المثال إذا كنتتستخدم أسماء الأشهر في برنامج فمن الأفضل في هذه الحالة استخدام المصفوفة بدلًا منالشعاع لأنك تعلم أنك بحاجة 12 عنصر فقط:

let months = ["January", "February", "March", "April", "May", "June", "July",

              "August", "September", "October", "November", "December"];

يُكتب نوع المصفوفة باستخدام الأقواس المعقوفة مع نوع العناصر ومن ثم فاصلة منقوطةوعدد العناصر ضمن المصفوفة كما هو موضح:

let a: [i32; 5] = [1, 2, 3, 4, 5];

يمثل النوع i32 في مثالنا هذا نوع عناصر المصفوفة، بينما يمثل العدد "5" الذي يقع بعدالفاصلة المنقوطة عدد عناصر المصفوفة الخمس.

يمكنك تهيئة المصفوفة بحيث تحمل القيمة ذاتها لكافة العناصر عن طريق تحديد القيمةالابتدائية initial value متبوعةً بفاصلة منقوطة ومن ثم طول المصفوفة ضمن أقواسمعقوفة، كما هو موضح:

let a = [3; 5];

ستحتوي المصفوفة a على 5 عناصر وستكون قيم العناصر جميعها مساوية إلى 3 مبدئيًا،وهذا الأمر مماثل لكتابة السطر البرمجي let a = [3, 3, 3 ,3 ,3];‎ إلا أن هذه الطريقةمختصرة.

الوصول إلى عناصر المصفوفة

تُمثل المصفوفة جزءًا واحدًا معلوم الحجم من الذاكرة، والذي يُمكن تخزينه في المكدس،ويمكنك الوصول إلى عناصر المصفوفة باستخدام الدليل كما هو موضح:

اسم الملف: src/main.rs

fn main() {

    let a = [1, 2, 3, 4, 5];


    let first = a[0];

    let second = a[1];

}

في مثالنا السابق، سيُسند إلى المتغير first القيمة الابتدائية 1 لأنها القيمة الموجودة فيالدليل [0] ضمن المصفوفة، بينما سيُسند إلى المتغير second القيمة 2 لأنها القيمةالموجودة في الدليل[1] ضمن المصفوفة.

محاولة الوصول الخاطئ إلى عناصر المصفوفة

دعنا نرى ما الذي سيحدث إذا حاولت الوصول إلى عنصر من عناصر المصفوفة إذا كان ذلكالعنصر يقع خارج المصفوفة بعد نهايتها، ولنقل أننا سننفّذ الشيفرة البرمجية التاليةالمشابهة للعبة التخمين في المقال السابق بالحصول على دليل المصفوفة من المستخدم:

اسم الملف: src/main.rs

use std::io;


fn main() {

    let a = [1, 2, 3, 4, 5];


    println!("Please enter an array index.");


    let mut index = String::new();


    io::stdin()

        .read_line(&mut index)

        .expect("Failed to read line");


    let index: usize = index

        .trim()

        .parse()

        .expect("Index entered was not a number");


    let element = a[index];


    println!("The value of the element at index {index} is: {element}");

}

ستُصرّف الشيفرة البرمجية بنجاح، وإذا شغلت البرنامج باستخدام cargo run وأدخلتالقيم 0 أو 1 أو 2 أو 3 أو 4، فسيطبع البرنامج القيمة الموافقة لهذا الدليل ضمن المصفوفة، إلاأنك ستحصل على الخرج التالي إذا حاولت إدخال قيمة أكبر من حجم المصفوفة (مثل 10):

thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

تسبب البرنامج بخطأ عند التشغيل runtime error عند إدخال قيمة خاطئة إلى عمليةالوصول للعناصر بالدليل، وانتهى البرنامج برسالة خطأ ولم يُنفّذ تعليمة !println الأخيرةتتفقد لغة راست الدليل الذي حدّدته عند محاولتك الوصول إليه فيما إذا كان أصغر من حجمالمصفوفة، وإذا كان الدليل أكبر أو يساوي حجم المصفوفة فسيهلع panic البرنامج، وتحدثعملية التفقد هذه عند وقت التشغيل خصوصًا في هذه الحالة وذلك لأن المصرف ربما لنيعرف القيمة التي سيدخلها المستخدم عند تشغيل الشيفرة بعد ذلك.

كان هذا مثالًا لمبادئ أمان ذاكرة رست بصورةٍ عملية، وتفتقر معظم لغات البرمجة منخفضة المستوى هذا النوع من التحقق، إذ يُمكن الوصول إلى ذاكرة خاطئة عندما تُعطي دليلًا خاطئًا في هذه اللغاتتحميك لغة رست من هذا النوع من الأخطاء بالخروج من البرنامج فورًا عوضًا عن السماح بالوصول إلى ذاكرة خاطئة والاستمرار بالبرنامج

الطول

ذو إشارة

عديم الإشارة

8-بت

i8

u8

16-بت

i16

u16

32-بت

i32

u32

64-بت

i64

u64

128-بت

i128

u128

يعتمد على معمارية الحاسب

isize

usize


العدد المجرد

مثال

عشري

98‎_222

ست عشري

0xff

ثُماني

0o77

ثُنائي

0b1111_0000

بايت (فقط بحجم u8)

b'A'‎

التعليقات (1)

قم بتسجيل الدخول لتتمكن من إضافة رد