اذهب إلى المحتوى

لغة رست غير الآمنة Unsafe Rust


Naser Dakhel

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

توجد رست غير الآمنة لأن التحليل الساكن بطبيعته متحفظ، أي عندما يحاول المصرّف تحديد ما إذا كانت الشيفرة البرمجية تدعم الضمانات أم لا، فمن الأفضل له رفض بعض البرامج الصالحة بدلًا من قبول بعض البرامج غير الصالحة. يمكن أن تكون الشيفرة البرمجية تكون جيدة، إلا أن مصرف رست سيرفض تصريف الشيفرة البرمجية إن لم يكن لديه معلومات كافية ليكون واثقًا، ويمكنك في هذه الحالات استعمال شيفرة غير آمنة لإخبار المصرف "صدقني، أعرف ما أفعله"، ومع ذلك كن حذرًا من استعمال رست غير الآمنة على مسؤوليتك الخاصة: إذا استعملت شيفرة غير آمنة على نحوٍ غير صحيح فقد تحدث بعض المشاكل بسبب عدم أمان الذاكرة مثل تحصيل dereferencing مؤشر فارغ.

السبب الآخر لوجود لغة رست غير آمنة هو أن عتاد الحاسب الأساسي غير آمن بطبيعته، فإذا لم تسمح لك رست بإنجاز عمليات غير آمنة فلن يمكنك إنجاز مهام معينة. تحتاج رست إلى السماح لك ببرمجة الأنظمة منخفضة المستوى مثل التفاعل المباشر مع نظام التشغيل أو حتى كتابة نظام التشغيل الخاص بك. يُعد العمل مع برمجة الأنظمة منخفضة المستوى أحد أهداف اللغة. لنكتشف ما يمكننا فعله مع رست غير الآمنة وكيفية إنجاز ذلك.

القوى الخارقة غير الآمنة unsafe superpowers

استخدم الكلمة المفتاحية unsafe للتبديل إلى رست غير الآمنة، ثم ابدأ كتلة جديدة تحتوي على الشيفرة غير الآمنة. يمكنك اتخاذ خمسة إجراءات في رست غير الآمنة لا يمكنك فعلها في رست الآمنة ونسميها القوى الخارقة غير الآمنة؛ تتضمن هذه القوى الخارقة القدرة على:

  • تحصيل مؤشر خام raw pointer.
  • استدعاء تابع أو دالة غير آمنين.
  • الوصول أو التعديل على متغير ساكن static متغيّر mutable.
  • تطبيق سمة trait غير آمنة.
  • حقول الوصول الخاصة بـ union.

من المهم أن نفهم أن unsafe لا توقف تشغيل مدقق الاستعارة أو تعطل أي من فحوصات أمان رست الأخرى؛ وإذا كنت تستخدم مرجعًا في شيفرة غير آمنة فسيظل التحقق منه جاريًا. تمنحك الكلمة المفتاحية unsafe فقط الوصول إلى هذه الميزات الخمس التي لم يجري التحقق منها بعد ذلك من المصرف من أجل سلامة الذاكرة، وستظل تتمتع بدرجة من الأمان داخل كتلة غير آمنة. لا تعني unsafe أن الشيفرة الموجودة داخل الكتلة هي بالضرورة خطيرة أو أنها ستواجه بالتأكيد مشكلات تتعلق بسلامة الذاكرة، القصد هو أنه بصفتك مبرمجًا ستضمن أن الشيفرة الموجودة داخل كتلة unsafe ستصل إلى الذاكرة بطريقة صالحة.

ليس البشر معصومين والأخطاء تحصل، ويمكنك من خلال إجبار وجود هذه العمليات الخمس غير الآمنة داخل كتل موصّفة unsafe أن تضمن بقاء أي أخطاء متعلقة بأمان الذاكرة داخل كتلة unsafe. اجعل كتل unsafe صغيرة، ستقدر ذلك لاحقًا عندما تفتش عن أخطاء الذاكرة.

لعزل الشيفرة غير الآمنة قدر الإمكان، يُفضّل تضمين الشيفرة غير الآمنة في عملية تجريد آمنة وتوفير واجهة برمجة تطبيقات آمنة التي سنناقشها لاحقًا في هذا الفصل عندما نتحدث عن الدوال والتوابع غير الآمنة. تُطبَّق أجزاء من المكتبة القياسية مثل تجريدات آمنة على الشيفرات البرمجية غير الآمنة التي قد جرى تدقيقها. يمنع تغليف الشيفرة غير الآمنة في عملية تجريد آمنة استخدامات unsafe من التسرب إلى جميع الأماكن التي قد ترغب أنت أو المستخدمين لديك في استخدام الوظيفة المطبقة بشيفرة unsafe لأن استخدام التجريد الآمن آمن.

لنلقي نظرةً على كل من القوى الخارقة الخمس غير الآمنة بالترتيب، سنلقي نظرةً أيضًا على بعض الأفكار المجردة التي تقدم واجهةً آمنةً للشيفرات البرمجية غير الآمنة.

تحصيل مرجع مؤشر خام

ذكرنا سابقًا في الفصل المراجع References والاستعارة Borrowing والشرائح Slices في لغة رست في قسم "المراجع المعلقة" أن المصرّف يضمن صلاحية المراجع دائمًا. تحتوي رست غير الآمنة على نوعين جديدين يدعيان المؤشرات الخام التي تشبه المراجع، فكما هو الحال مع المراجع يمكن أن تكون المؤشرات الخام ثابتة immutable أو متغيّرة mmutable وتُكتب بالطريقة const T* و mut T* على التوالي. لا تمثّل علامة النجمة عامل التحصيل وإنما هي جزءٌ من اسم النوع. يُقصد بمصطلح الثابت في سياق المؤشرات الخام أنه لا يمكن تعيين المؤشر مباشرةً بعد تحصيله.

تختلف المؤشرات الأولية عن المراجع والمؤشرات الذكية بما يلي:

  • يُسمح بتجاهل قواعد الاستعارة من خلال وجود مؤشرات ثابتة أو متغيّرة أو مؤشرات متعددة متغيّرة إلى الموقع ذاته.
  • ليست مضمونة للإشارة إلى ذاكرة صالحة.
  • من المسموح أن تكون فارغة.
  • لا تطبق أي تحرير ذاكرة تلقائي.

يمكنك التخلي عن الأمان المضمون من خلال تجاهل الضمانات التي تقدمها رست وذلك مقابل أداء أفضل أو القدرة على التفاعل مع لغة أو عتاد آخر لا تنطبق عليه ضمانات رست.

توضح الشيفرة 1 كيفية إنشاء مؤشر خام ثابت ومتغيّر من المراجع.

    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

[الشيفرة 1: إنشاء مؤشرات خام من المراجع]

لاحظ أننا لا نضمّن الكلمة المفتاحية unsafe في هذه الشيفرة. يمكننا إنشاء مؤشرات خام في شيفرة آمنة، ولا يمكننا تحصيل المؤشرات الخام خارج كتلة غير آمنة كما سترى بعد قليل.

أنشأنا مؤشرات خام باستعمال as لتحويل مرجع ثابت ومتغيّر إلى أنواع المؤشرات الخام الخاصة بهما، ونظرًا إلى أننا أنشأناها مباشرةً من مراجع مضمونة لتكون صالحة فنحن نعلم أن هذه المؤشرات الخام المعنيّة صالحة لكن لا يمكننا افتراض هذا حول أي مؤشر خام.

لإثبات ذلك سننشئ مؤشر خام لا يمكننا التأكد من صحته. تظهر الشيفرة 2 كيفية إنشاء مؤشر خام يشير إلى موقع عشوائي في الذاكرة. محاولة استخدام الذاكرة العشوائية هي عملية غير معرّفة، إذ قد توجد بيانات في ذلك العنوان أو قد لا تكون موجودة، ومن الممكن للمصرّف أن يحسن الشيفرة بحيث لا يكون هناك وصول للذاكرة أو قد يخطئ البرنامج في وجود خطأ في التجزئة segmentation. لا يوجد عادةً سببٌ جيد لكتابة شيفرة مثل هذه، لكن هذا ممكن.

    let address = 0x012345usize;
    let r = address as *const i32;

[الشيفرة 2: إنشاء مؤشر خام إلى عنوان ذاكرة عشوائي]

تذكر أنه يمكننا إنشاء مؤشرات خام في شيفرة آمنة لكن لا يمكننا تحصيل المؤشرات الخام وقراءة البيانات التي يُشار إليها، ونستخدم في الشيفرة 3 عامل التحصيل * على مؤشر خام يتطلب كتلة unsafe.

    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
    }

[الشيفرة 3: تحصيل المؤشرات الخام ضمن كتلة unsafe]

لا يضرّ إنشاء مؤشر؛ إذ يكمن الضرر فقط عندما نحاول الوصول إلى القيمة التي تشير إليها، فقد ينتهي بنا الأمر بالتعامل مع قيمة غير صالحة.

لاحظ أيضًا أننا أنشأنا في الشيفرة 1 و3 مؤشرات خام من النوع const i32* و mut i32* التي أشارت كلتاهما إلى موقع الذاكرة ذاته، حيث يُخزَّن num. إذا حاولنا إنشاء مرجع ثابت ومتغيّر إلى num بدلًا من ذلك، فلن تُصرّف الشيفرة لأن قواعد ملكية رست لا تسمح بمرجع متغيّر في الوقت ذاته كما هو الحال مع أي مراجع ثابتة. يمكننا باستخدام المؤشرات الخام إنشاء مؤشر متغيّر ومؤشر ثابت للموقع ذاته وتغيير البيانات من خلال المؤشر المتغيّر مما قد يؤدي إلى إنشاء سباق بيانات data race. كن حذرًا.

مع كل هذه المخاطر، لماذا قد تستخدم المؤشرات الخام؟ إحدى حالات الاستخدام الرئيسية هي عند التفاعل مع شيفرة سي C كما سترى لاحقًا في القسم "استدعاء دالة أو تابع غير آمنين"، وهناك حالة أخرى عند بناء تجريدات آمنة لا يفهمها مدقق الاستعارة. سنقدم دالات غير آمنة، ثم سنلقي نظرةً على مثال على التجريد الآمن الذي يستعمل شيفرةً غير آمنة.

استدعاء تابع أو دالة غير آمنين

النوع الثاني من العمليات التي يمكنك إجراؤها في كتلة غير آمنة هو استدعاء دالات غير آمنة، إذ تبدو الدالات والتوابع غير الآمنة تمامًا مثل الدالات والتوابع العادية ولكنها تحتوي على unsafe إضافية قبل بقية التعريف. تشير الكلمة المفتاحية unsafe في هذا السياق إلى أن الدالة لها متطلبات نحتاج إلى دعمها عند استدعاء هذه الدالة لأن رست لا يمكن أن تضمن أننا استوفينا هذه المتطلبات؛ فمن خلال استدعاء دالة غير آمنة داخل كتلة unsafe، فإننا نقول إننا قد قرأنا توثيق هذه الدالة ونتحمل مسؤولية دعم مواصفات الدالة هذه.

فيما يلي دالة غير آمنة تدعى dangerous لا تنفّذ أي شيء داخلها:

    unsafe fn dangerous() {}

    unsafe {
        dangerous();
    }

يجب علينا استدعاء دالة dangerous داخل كتلة unsafe منفصلة، وإذا حاولنا استدعاء dangerous دون unsafe سنحصل على خطأ:

$ cargo run
   Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
 --> src/main.rs:4:5
  |
4 |     dangerous();
  |     ^^^^^^^^^^^ call to unsafe function
  |
  = note: consult the function's documentation for information on how to avoid undefined behavior

For more information about this error, try `rustc --explain E0133`.
error: could not compile `unsafe-example` due to previous error

نؤكد لرست مع الكتلة unsafe أننا قرأنا توثيق الدالة ونفهم كيفية استخدامها صحيحًا وتحققنا من أننا نفي بمواصفات الدالة.

يعدّ محتوى الدالات غير الآمنة بمثابة كتل unsafe، لذا لا نحتاج إلى إضافة كتلة unsafe أخرى لأداء عمليات أخرى غير آمنة ضمن دالة غير آمنة.

إنشاء تجريد آمن على شيفرة غير آمنة

لا يعني احتواء الدالة على شيفرة غير آمنة أننا بحاجة إلى وضع علامة بأن كامل الدالة غير آمنة، إذ يُعد تغليف الشيفرات البرمجية غير الآمنة في دالة آمنة تجريدًا شائعًا. وكمثال دعنا ننظر إلى الدالة split_at_mut الموجودة في المكتبة القياسية التي تتطلب بعض الشيفرات البرمجية غير الآمنة. سنكتشف كيف يمكننا تنفيذها. يُعرَّف هذا التابع الآمن على الشرائح المتغيّرة، فهو يأخذ شريحةً واحدة ويحوّلها لشريحتين عن طريق تقسيم الشريحة في الدليل المعطى مثل وسيط. توضح الشيفرة 4 كيفية استخدام split_at_mut.

    let mut v = vec![1, 2, 3, 4, 5, 6];

    let r = &mut v[..];

    let (a, b) = r.split_at_mut(3);

    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);

[الشيفرة 4: استعمال الدالة الآمنة split_at_mut]

لا يمكننا تنفيذ هذه الدالة باستعمال رست الآمنة فقط، وقد تبدو المحاولة السابقة مثل الشيفرة 5 التي لن تصرف. سننفّذ split_at_mut مثل دالة للتبسيط بدلًا من تابع لشرائح قيم i32 فقط بدلًا من النوع العام T.

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();

    assert!(mid <= len);

    (&mut values[..mid], &mut values[mid..])
}

[الشيفرة 5: محاولة تنفيذ split_at_mut فقط باستعمال رست الآمنة]

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

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

عندما نحاول تصريف الشيفرة البرمجية في الشيفرة 5 سنحصل على خطأ.

$ cargo run
   Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
error[E0499]: cannot borrow `*values` as mutable more than once at a time
 --> src/main.rs:6:31
  |
1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
  |                         - let's call the lifetime of this reference `'1`
...
6 |     (&mut values[..mid], &mut values[mid..])
  |     --------------------------^^^^^^--------
  |     |     |                   |
  |     |     |                   second mutable borrow occurs here
  |     |     first mutable borrow occurs here
  |     returning this value requires that `*values` is borrowed for `'1`

For more information about this error, try `rustc --explain E0499`.
error: could not compile `unsafe-example` due to previous error

لا يستطيع مدقق الاستعارة في رست أن يفهم أننا نستعير أجزاءً مختلفةً من الشريحة، إذ أنه يعرف فقط أننا نستعير من الشريحة نفسها مرتين. تعد عملية استعارة أجزاء مختلفة من الشريحة أمرًا مقبولًا بصورةٍ أساسية لأن الشريحتين غير متداخلتين لكن رست ليست ذكية بما يكفي لمعرفة ذلك. عندما نعلم أن الشيفرة على ما يرام لكن رست لا تعلم ذلك فهذا يعني أن الوقت قد حان لاستخدام شيفرة غير آمنة.

توضح الشيفرة 6 كيفية استخدام كتلة unsafe ومؤشر خام وبعض الاستدعاءات للدالات غير الآمنة لجعل تنفيذ split_at_mut يعمل.

use std::slice;

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

[الشيفرة 6: استعمال شيفرة غير آمنة في تنفيذ دالة split_at_mut]

تذكر سابقًا من قسم "نوع الشريحة" في الفصل المراجع References والاستعارة Borrowing والشرائح Slices في لغة رست أن الشرائح هي مؤشرات لبعض البيانات وطول الشريحة.

نستعمل تابع len للحصول على طول الشريحة وتابع as_mut_ptr للوصول إلى المؤشر الخام للشريحة، وفي هذه الحالة نظرًا لأن لدينا شريحة متغيّرة لقيم i32 فإن as_mut_ptr تُعيد مؤشرًا خامًا من النوع mut i32* وهو الذي خزّناه في المتغير ptr.

نحافظ على التأكيد على أن الدليل mid يقع داخل الشريحة، ثم نبدأ بكتابة الشيفرة غير الآمنة: تأخذ الدالة slice::from_raw_parts_mut مؤشرًا خامًا وطولًا وتنشئ شريحة. نستخدم هذه الدالة لإنشاء شريحة تبدأ من ptr وتكون عناصرها بطول mid، ثم نستدعي التابع add على ptr مع الوسيط mid للحصول على مؤشر خام يبدأ من mid وننشئ شريحةً باستخدام هذا المؤشر والعدد المتبقي من العناصر بعد mid ليكون طول الشريحة.

الدالة slice::from_raw_parts_mut غير آمنة لأنها تأخذ مؤشرًا خامًا ويجب أن تثق في أن هذا المؤشر صالح، كما يعد التابع add في المؤشرات الخام غير آمن أيضًا لأنه يجب أن تثق في أن موقع الإزاحة هو أيضًا مؤشر صالح، لذلك كان علينا وضع كتلة unsafe حول استدعاءات slice::from_raw_parts_mut و addحتى نتمكن من استدعائها. من خلال النظر إلى الشيفرة وإضافة التأكيد على أن mid يجب أن يكون أقل من أو يساوي len يمكننا أن نقول أن جميع المؤشرات الخام المستخدمة داخل الكتلة unsafe ستكون مؤشرات صالحة للبيانات داخل الشريحة، وهذا استخدام مقبول ومناسب للكتلة unsafe.

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

في المقابل، من المحتمل أن يتعطل استخدام slice::from_raw_parts_mut في الشيفرة 7 عند استعمال الشريحة. تأخذ هذه الشيفرة موقعًا عشوائيًا للذاكرة وتنشئ شريحة يبلغ طولها 10000 عنصر.

    use std::slice;

    let address = 0x01234usize;
    let r = address as *mut i32;

    let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) };

[الشيفرة 7: إنشاء شريحة من مكان ذاكرة عشوائي]

لا نمتلك الذاكرة في هذا الموقع العشوائي وليس هناك ما يضمن أن الشريحة التي تنشئها هذه الشيفرة تحتوي على قيم i32 صالحة، كما تؤدي محاولة استخدام values كما لو كانت شريحة صالحة إلى سلوك غير معرّف.

استعمال دوال extern لاستدعاء شيفرة خارجية

قد تحتاج شيفرة رست الخاصة بك أحيانًا إلى التفاعل مع شيفرة مكتوبة بلغة برمجية أخرى، لهذا تحتوي رست على الكلمة المفتاحية extern التي تسهل إنشاء واستخدام واجهة الدالة الخارجية Foreign Function interface‎ -أو اختصارًا FFI، وهي طريقة للغة البرمجة لتعريف الدوال وتمكين لغة برمجة (خارجية) مختلفة لاستدعاء هذه الدوال.

توضح الشيفرة 8 التكامل مع دالة abs من مكتبة سي القياسية، وغالبًا ما تكون الدوال المعلنة داخل الكتل extern غير آمنة لاستدعائها من شيفرة رست، والسبب هو أن اللغات الأخرى لا تفرض قواعد وضمانات رست ولا يمكن لرست التحقق منها لذلك تقع مسؤولية ضمان سلامتها على عاتق المبرمج.

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

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

[الشيفرة 8: التصريح عن الدالة extern واستدعاؤها في لغة أخرى]

نُدرج ضمن كتلة ''extern ''C أسماء وبصمات signature الدوال الخارجية من لغة أخرى نريد استدعائها، إذ يحدد الجزء "C" واجهة التطبيق الثنائية application binary interface‎ -أو اختصارًا ABI- التي تستخدمها الدالة الخارجية. تعرّف واجهة التطبيق الثنائية ABI كيفية استدعاء الدالة على مستوى التجميع assembly، وتعد واجهة التطبيق الثنائية للغة ''C'' الأكثر شيوعًا وتتبع واجهة التطبيق الثنائية للغة البرمجة سي.

استدعاء دوال رست من لغات أخرى

يمكننا أيضًا استخدام extern لإنشاء واجهة تسمح للغات الأخرى باستدعاء دوال رست، وبدلًا من إنشاء كتلة extern كاملة نضيف الكلمة المفتاحية extern ونحدد واجهة التطبيق الثنائية ABI لاستخدامها قبل الكلمة المفتاحية fn للدالة ذات الصلة. نحتاج أيضًا إلى إضافة تعليق توضيحي [no_mangle]# لإخبار مصرّف رست بعدم تشويه mangle اسم هذه الدالة؛ إذ يحدث التشويه عندما يغير المصرف الاسم الذي أعطيناه للدالة لاسم مختلف يحتوي على مزيد من المعلومات لأجزاء أخرى من عملية التصريف لاستهلاكها ولكنها أقل قابلية للقراءة من قبل الإنسان. يشكّل كل مصرف لغة برمجية الأسماء على نحوٍ مختلف قليلًا، لذلك لكي تكون دالة رست قابلة للتسمية من اللغات الأخرى، يجب علينا تعطيل تشويه الاسم في مصرف رست.

في المثال التالي نجعل دالة call_from_c قابلة للوصول من شيفرة مكتوبة بلغة سي بعد تصريفها في مكتبة مشتركة وربطها من لغة سي:

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

لا يتطلب استعمال extern الكتلة unsafe.

الوصول أو تعديل متغير ساكن قابل للتغيير mutable

لم نتحدث بعد عن المتغيرات العامة global التي تدعمها رست، إلا أنها قد تسبب مشكلةً مع قواعد ملكية رست. إذا كان هناك خيطان thread يصلان إلى نفس المتغير العام المتغيّر فقد يتسبب ذلك في حدوث سباق بيانات data race.

تسمى المتغيرات العامة في رست بالمتغيرات الساكنة، وتظهر الشيفرة 9 مثالًا للتصريح عن متغير ساكن واستخدامه مع شريحة سلسلة نصية مثل قيمة.

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

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("name is: {}", HELLO_WORLD);
}

[الشيفرة 9: تعريف واستعمال متغير ساكن ثابت]

تشبه المتغيرات الساكنة الثوابت التي ناقشناها سابقًا في الفصل المتغيرات والتعديل عليها في لغة رست. أسماء المتغيرات الثابتة موجودة في SCREAMING_SNAKE_CASE اصطلاحًا، ويمكن للمتغيرات الساكنة فقط تخزين المراجع مع دورة حياة ساكنة static'، ما يعني أن مصرف رست يمكنه معرفة دورة الحياة الخاصة دون الحاجة لتحديده صراحةً، ويعد الوصول إلى متغير ساكن آمنًا.

الفرق الدقيق بين الثوابت والمتغيرات الساكنة الثابتة immutable هو أن القيم في متغير ساكن لها عنوان ثابت في الذاكرة، كما سيؤدي استعمال القيمة دائمًا إلى الوصول إلى البيانات ذاتها. من ناحية أخرى، يُسمح للثوابت بتكرار بياناتها في أي وقت تُستخدم، الفرق الآخر هو أن المتغيرات الساكنة يمكن أن تكون متغيّرة. الوصول إلى المتغيرات الساكنة القابلة للتغيير وتعديلها غير آمن. توضح الشيفرة 10 كيفية التصريح عن متغير ساكن قابل للتغيير يسمى COUNTER والوصول إليه وتعديله.

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

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

[الشيفرة 10: القراءة من أو الكتابة على متغير ساكن قابل للتغيير غير آمن]

كما هو الحال مع المتغيرات العادية نحدد قابلية التغيير باستخدام الكلمة المفتاحية mut، ويجب أن تكون أي شيفرة تقرأ أو تكتب من COUNTER ضمن كتلة unsafe. تُصرَّف هذه الشيفرة وتطبع COUNTER: 3 كما نتوقع لأنها تستخدم خيطًا واحدًا، إذ من المحتمل أن يؤدي وصول خيوط متعددة إلى COUNTER إلى سباق البيانات.

يصعب ضمان عدم وجود سباقات بيانات مع البيانات المتغيّرة التي يمكن الوصول إليها بصورةٍعامة، ولهذا السبب تنظر رست إلى المتغيرات الساكنة المتغيّرة بكونها غير آمنة. يُفضّل استخدام تقنيات التزامن والمؤشرات الذكية ذات الخيوط الآمنة التي ناقشناها سابقًا في الفصل استخدام الخيوط Threads لتنفيذ شيفرات رست بصورة متزامنة آنيًا حيثما أمكن حتى يتحقق المصرف من أن البيانات التي يجري الوصول إليها من الخيوط المختلفة آمنة.

تنفيذ سمة غير آمنة

يمكننا استعمال unsafe لتطبيق سمة غير آمنة؛ وتكون السمة غير آمنة عندما يحتوي أحد توابعها على الأقل على بعض اللامتغايرات invariant التي لا يستطيع المصرف التحقق منها. نصرّح بأن السمة unsafe عن طريق إضافة الكلمة المفتاحية unsafe قبل trait ووضع علامة على أن تنفيذ السمة unsafe أيضًا كما هو موضح في الشيفرة 11.

unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}

[الشيفرة 11: تعريف وتنفيذ سمة غير آمنة]

نعد بأننا سنلتزم باللا متغايرات التي لا يمكن للمصرف التحقق منها باستخدام unsafe impl.

على سبيل المثال، تذكر سمات العلامة Sync و Send التي ناقشناها سابقًا في قسم "التزامن الموسع مع السمة Sync والسمة Send" في الفصل تزامن الحالة المشتركة Shared-State Concurrency في لغة رست وتوسيع التزامن مع Send و Sync، يطبّق المصرف هذه السمات تلقائيًا إذا كانت أنواعنا تتكون كاملًا من النوعين Sync و Send. إذا طبقنا نوعًا يحتوي على نوع ليس Sync و Send مثل المؤشرات الخام ونريد وضع علامة على هذا النوع على أنه Sync و Send فيجب علينا استخدام unsafe. لا تستطيع رست التحقق من أن النوع الخاص بنا يدعم الضمانات التي يمكن إرسالها بأمان عبر الخيوط أو الوصول إليها من خيوط متعددة، لذلك نحتاج إلى إجراء تلك الفحوصات يدويًا والإشارة إلى ذلك باستخدام unsafe.

الوصول لحقول الاتحاد Union

الإجراء الأخير الذي يعمل فقط مع unsafe هو الوصول إلى حقول الاتحاد؛ ويعد union مشابهًا للبنية struct ولكن يُستخدم فيه حقل مصرح واحد فقط في نسخة معينة في وقت واحد، وتُستخدم الاتحادات بصورةٍ أساسية للتفاعل مع الاتحادات في شيفرة لغة سي. يعد الوصول إلى حقول الاتحاد غير آمن لأن رست لا يمكنها ضمان نوع البيانات المخزنة حاليًا في نسخة الاتحاد. يمكنك معرفة المزيد عن الاتحادات في توثيق رست Rust Reference.

متى تستعمل شيفرة غير آمنة؟

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

ترجمة -وبتصرف- لقسم من الفصل Advanced Features من كتاب The Rust Programming Language.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...