
引言
尽管具有全面的数据安全意识形态, 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
如果类型T
和U
具有不同的长度,则会发生编译错误。 参数和返回值都不能为无效值 。
未定义的行为
您可能已经注意到,此功能被标记为不安全,这是合乎逻辑的,因为由于许多因素,它可能产生未定义的行为 :
- 创建状态无效的任何类型的实例;
- 创建具有无效值的原语;
- 为了满足类型推断,如果未指定,则可能会出现完全意外的输出类型。
non-repr(C)
类型之间的转换;- 转换为没有明确指定生存时间的常规链接会导致不相关的生存时间 ;
- 将不可变链接转换为可变链接。
开放机会
但是,此技术在某些特殊情况下证明其自身合理性,例如,获得浮点数据类型的位模式(或更普遍的是,类型为punning,其中T
和U
不是原始指针):
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) };
还可以使用安全方法,与对mem::transmute<T, U>
的类似调用进行相同的操作:
并通过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};
换句话说,仅在没有其他帮助的情况下才证明使用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>
,如果类型T
和U
字节长度不同,则当前的模拟不会产生编译错误,但强烈建议仅在大小相同的情况下调用它。
还应该记住,如果类型U
的大小超过T
的大小,则此函数将生成未定义的行为。
结论
再次提醒您,只有在您完全无法使用这些功能的情况下,才应使用这些功能。 正如Nomicon所说,这实际上是您在Rust中可以做的最不安全的事情。
本文的所有示例均摘自 mem::transmute<T, U>
,还使用了此处和此处的材料。 我希望这篇文章对您有用。