تحويلات نوع البيانات السحرية في Rust: Intrinsika mem :: transmute


مقدمة


إن لغة برمجة 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) }; //   `as`  .  ,  //   `as    let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) }; 

ويمكن أيضًا استخدام الطرق الآمنة ، وهي تفعل نفس الشيء مثل استدعاء مماثل لـ mem::transmute<T, U> :


 //        //    let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") }; assert_eq!(slice, &[82, 117, 115, 116]); //      `as_bytes()`,  //     let slice = "Rust".as_bytes(); assert_eq!(slice, &[82, 117, 115, 116]); //      //   assert_eq!(b"Rust", &[82, 117, 115, 116]); 

slice::split_at_mut() هذا الكود المصدري من خلال ثلاثة تطبيقات slice::split_at_mut() : استخدام mem::transmute<T, U> ، slice::from_raw_parts() .


 use std::{slice, mem}; //     ,    //     fn split_at_mut_transmute<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let slice2 = mem::transmute::<&mut [T], &mut [T]>(slice); // -, mem::transmute<T, U>   , //  ,    -    T  U. // // -,      ,  //        (&mut slice[0..mid], &mut slice2[mid..len]) } } //       ; `&mut *` //    `&mut T`  `&mut T`  `*mut T`. fn split_at_mut_casts<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let slice2 = &mut *(slice as *mut [T]); //        ,  //        (&mut slice[0..mid], &mut slice2[mid..len]) } } //    ,    //  fn split_at_stdlib<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { let len = slice.len(); assert!(mid <= len); unsafe { let ptr = slice.as_mut_ptr(); //      ,  //    : `slice`, r-value ret.0  r-value ret.1. // // `slice`     `let ptr = ...`,   // ,    "",  ,  //      . (slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid)) } } 

بمعنى آخر ، يكون استخدام 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> ، والمواد من هنا وهنا استخدمت أيضًا. آمل أن تكون المقالة مفيدة لك.

Source: https://habr.com/ru/post/ar448240/


All Articles