Rust中的魔术数据类型转换:Intrinsika mem :: transmute


引言


尽管具有全面的数据安全意识形态, Rust编程语言也具有不安全的编程方法,因为有时它们可​​以通过消除不必要的计算来提高速度,有时这是至关重要的。


其中之一是我们当前的实例-内在函数mem::transmute<T, U> ,为它们两个设计了一下,在极其不寻常的情况下派上用场。


功能说明


mem::transmute<T, U>由编译器直接实现,因为根据定义,它不能用Rust语言的语法描述。 它只是将一种数据类型的位重新解释为另一种数据位:


 pub unsafe extern "rust-intrinsic" fn transmute<T, U>(e: T) -> U 

在重新解释之前,此函数将接管数据类型T的重新解释变量的所有权,然后,它“忘记”它(但不调用相应的析构函数)。


不会在物理内存中进行任何复制,因为这种内在特性使编译器清楚地知道类型T的内容实际上是类型U


如果类型TU具有不同的长度,则会发生编译错误。 参数和返回值都不能为无效值


未定义的行为


您可能已经注意到,此功能被标记为不安全,这是合乎逻辑的,因为由于许多因素,它可能产生未定义的行为


  • 创建状态无效的任何类型的实例;
  • 创建具有无效值的原语;
  • 为了满足类型推断,如果未指定,则可能会出现完全意外的输出类型。
  • non-repr(C)类型之间的转换;
  • 转换为没有明确指定生存时间的常规链接会导致不相关的生存时间
  • 将不可变链接转换为可变链接。

开放机会


但是,此技术在某些特殊情况下证明其自身合理性,例如,获得浮点数据类型的位模式(或更普遍的是,类型为punning,其中TU不是原始指针):


 let bitpattern = unsafe { std::mem::transmute::<f32, u32>(1.0) }; assert_eq!(bitpattern, 0x3F800000); 

将原始指针转换为指向函数的指针。 值得注意的是,该技术在函数指针和常规指针大小不同的平台之间不可移植。


 fn foo() -> i32 { 0 } let pointer = foo as *const (); let function = unsafe { std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0); 

延长寿命或缩短不变寿命。 这是非常不安全的,同时具有高级Rust语法:


 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()函数的三种实现来演示此源代码:使用mem::transmute<T, U> slice::split_at_mut() mem::transmute<T, U> as运算符以及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>合理的(在某些语言中用反射进行类比是适当的)。


变体内存:: transmute_copy <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> ,如果类型TU字节长度不同,则当前的模拟不会产生编译错误,但强烈建议仅在大小相同的情况下调用它。


还应该记住,如果类型U的大小超过T的大小,则此函数将生成未定义的行为。


结论


再次提醒您,只有在您完全无法使用这些功能的情况下,才应使用这些功能。 正如Nomicon所说,这实际上是您在Rust中可以做的最不安全的事情。


本文的所有示例均摘自 mem::transmute<T, U> ,还使用了此处此处的材料。 我希望这篇文章对您有用。

Source: https://habr.com/ru/post/zh-CN448240/


All Articles