
مقدمة
إن لغة برمجة Rust ، على الرغم من الإيديولوجية الشاملة لأمن البيانات ، لديها أيضًا طرق برمجة غير آمنة ، لأنها في بعض الأحيان يمكن أن تزيد من السرعة عن طريق التخلص من العمليات الحسابية غير الضرورية ، وفي بعض الأحيان تكون مجرد ضرورة حيوية.
أحد هذه الأمثلة هو حالتنا الحالية - mem::transmute<T, U>
intrinsics mem::transmute<T, U>
، المصممة لكليهما قليلاً ، وتكون مفيدة في مواقف غير عادية للغاية.
الوصف الوظيفي
mem::transmute<T, U>
مباشرة من قبل المترجم ، لأنه بحكم التعريف لا يمكن وصفه بواسطة بناء جملة لغة الصدأ. ببساطة يعيد تفسير وحدات بت من نوع بيانات مثل وحدات بت:
pub unsafe extern "rust-intrinsic" fn transmute<T, U>(e: T) -> U
قبل إعادة التفسير ، تتولى هذه الوظيفة ملكية المتغير المعاد تفسيره من نوع البيانات T
، وبعد ذلك "تنسى" (ولكن دون استدعاء المدمر المقابل).
لا يحدث أي نسخ في الذاكرة الفعلية ، لأن هذا المضمّن فقط يوضح للمترجم أن محتويات النوع T
هي في الواقع من النوع U
سيحدث خطأ في الترجمة إذا كان للأنواع T
و U
أطوال مختلفة. لا يمكن أن تكون الوسيطة ولا قيمة الإرجاع قيمتين غير صحيحتين .
سلوك غير محدد
كما لاحظت ، تم تمييز هذه الوظيفة على أنها غير آمنة ، وهذا أمر منطقي ، لأنه يمكن أن يولد سلوكًا غير محدد بسبب العديد من العوامل:
- إنشاء مثيل من أي نوع بحالة غير صالحة ؛
- خلق بدائية مع قيمة غير صالحة.
- من أجل تلبية استنتاج الكتابة ، يكون نوع الإخراج غير متوقع تمامًا ممكنًا إذا لم يتم تحديده ؛
- التحويلات بين أنواع
non-repr(C)
؛ - التحويل إلى رابط منتظم دون وقت حياة محدد بشكل صريح يؤدي إلى وقت حياة غير مرتبط ؛
- تحويل رابط ثابت إلى رابط قابل للتغيير.
فرص مفتوحة
ومع ذلك ، فإن هذه التقنية تبرر نفسها في بعض الحالات الخاصة ، على سبيل المثال ، الحصول على نمط بت من نوع بيانات الفاصلة العائمة (أو ، بشكل أعم ، كتابة التزاوج ، حيث لا يكون T
و U
مؤشرين أوليين):
let bitpattern = unsafe { std::mem::transmute::<f32, u32>(1.0) }; assert_eq!(bitpattern, 0x3F800000);
تحويل مؤشر raw إلى مؤشر إلى دالة. تجدر الإشارة إلى أن هذه التقنية ليست محمولة بين تلك المنصات حيث تختلف مؤشرات الوظائف والمؤشرات العادية في الحجم.
fn foo() -> i32 { 0 } let pointer = foo as *const (); let function = unsafe { std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0);
تمديد فترة الحياة أو تقصير وقت الحياة الثابت. هذا غير آمن للغاية وفي الوقت نفسه بناء جملة الصدأ:
struct R<'a>(&'a i32); unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> { std::mem::transmute::<R<'b>, R<'static>>(r) } unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> { std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r) }
توفير بدائل
في الأساس ، يمكن as
أن يمنع دون أي جهد السلوك غير المحدد الناجم عن mem::transmute<T, U>
:
let ptr = &mut 0; let val_transmuted = unsafe { std::mem::transmute::<&mut i32, &mut u32>(ptr) };
ويمكن أيضًا استخدام الطرق الآمنة ، وهي تفعل نفس الشيء مثل استدعاء مماثل لـ mem::transmute<T, U>
:
slice::split_at_mut()
هذا الكود المصدري من خلال ثلاثة تطبيقات slice::split_at_mut()
: استخدام mem::transmute<T, U>
، slice::from_raw_parts()
.
use std::{slice, mem};
بمعنى آخر ، يكون استخدام mem::transmute<T, U>
له ما يبرره فقط عندما لا يوجد شيء آخر يساعد (التشبيه مع التفكير في بعض اللغات مناسب هنا).
تباين mem :: transmute_copy <T، U>
قد لا تكون mem::transmute<T, U>
الداخلية المعتادة mem::transmute<T, U>
مناسبة في الحالات التي يكون فيها نقل ملكية متغير من النوع T
مستحيلاً. يأتي تنوعها mem::transmute_copy<T, U>
في عملية الإنقاذ:
pub unsafe fn transmute_copy<T, U>(src: &T) -> U
كما كنت قد خمنت ، بدلاً من تحريك وسيطة واحدة ، فإنه يجعل نسخة كاملة منه وينقل ملكية النتيجة. سيعمل هذا الاختلاف بشكل أبطأ ، لذلك يوصى باستخدامه بشكل أقل.
بخلاف mem::transmute<T, U>
، لا يولد التماثلية الحالية خطأ في mem::transmute<T, U>
البرمجي إذا كان للنوعين T
و U
أطوال مختلفة بالبايت ، لكن يوصى بشدة باستدعائها فقط إذا كان لها نفس الحجم.
تجدر الإشارة أيضًا إلى أنه إذا تجاوز حجم النوع U
حجمًا T
، فإن هذه الوظيفة تنشئ سلوكًا غير محدد.
استنتاج
مرة أخرى ، لاحظت أن الوظائف المعنية يجب أن تستخدم فقط في تلك الحالات التي لا يمكنك الاستغناء عنها. كما يقول Nomicon ، هذا هو بالفعل أكثر شيء غير آمن يمكنك القيام به في Rust.
جميع الأمثلة من هذه المقالة مأخوذة من mem::transmute<T, U>
، والمواد من هنا وهنا استخدمت أيضًا. آمل أن تكون المقالة مفيدة لك.