Rust std::mem::transmute实例讲解
时间:2022-04-08
本文章向大家介绍Rust std::mem::transmute实例讲解,主要分析其语法、参数、返回值和注意事项,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
用法
pub const unsafe extern "rust-intrinsic" fn transmute(e:T) -> U
将一种类型的值的位重新解释为另一种类型。
两种类型必须具有相同的大小。原始值和结果都不是无效值。
transmute 在语义上等同于将一种类型按位移动到另一种类型。它将源值中的位复制到目标值中,然后忘记原始值。它相当于 C 的 memcpy ,就像 transmute_copy 一样。
因为transmute是 by-value 操作,转换值本身的对齐不是问题。与任何其他函数一样,编译器已经确保T和U正确对齐。但是,当转换值时指向别处(例如指针、引用、框…),调用者必须确保正确对齐指向的值。
transmute 非常不安全。有很多方法可以使用此函数导致未定义的行为。 transmute 应该是绝对的最后手段。
在const 上下文中转换指向整数的指针是未定义的行为。任何将结果值用于整数运算的尝试都将中止const-evaluation。
nomicon 有额外的文档。
例子
transmute 在某些方面非常有用。
将指针转换为函数指针。这对于函数指针和数据指针具有不同大小的机器是不可移植的。
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<'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)
}
备择方案
不要绝望:transmute 的许多用途可以通过其他方式实现。以下是transmute 的常见应用,可以用更安全的构造替换。
将原始字节(&[u8])转换为 u32、f64 等:
let raw_bytes = [0x78, 0x56, 0x34, 0x12];
let num = unsafe {
std::mem::transmute::<[u8; 4], u32>(raw_bytes)
};
// use `u32::from_ne_bytes` instead
let num = u32::from_ne_bytes(raw_bytes);
// or use `u32::from_le_bytes` or `u32::from_be_bytes` to specify the endianness
let num = u32::from_le_bytes(raw_bytes);
assert_eq!(num, 0x12345678);
let num = u32::from_be_bytes(raw_bytes);
assert_eq!(num, 0x78563412);
将指针变成 usize :
let ptr = &0;
let ptr_num_transmute = unsafe {
std::mem::transmute::<&i32, usize>(ptr)
};
// Use an `as` cast instead
let ptr_num_cast = ptr as *const i32 as usize;
将 *mut T 变成 &mut T :
let ptr:*mut i32 = &mut 0;
let ref_transmuted = unsafe {
std::mem::transmute::<*mut i32, &mut i32>(ptr)
};
// Use a reborrow instead
let ref_casted = unsafe { &mut *ptr };
将 &mut T 变成 &mut U :
let ptr = &mut 0;
let val_transmuted = unsafe {
std::mem::transmute::<&mut i32, &mut u32>(ptr)
};
// Now, put together `as` and reborrowing - note the chaining of `as`
// `as` is not transitive
let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };
将 &str 变成 &[u8] :
// this is not a good way to do this.
let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") };
assert_eq!(slice, &[82, 117, 115, 116]);
// You could use `str::as_bytes`
let slice = "Rust".as_bytes();
assert_eq!(slice, &[82, 117, 115, 116]);
// Or, just use a byte string, if you have control over the string
// literal
assert_eq!(b"Rust", &[82, 117, 115, 116]);
将 Vec<&T> 变成 Vec
要转换容器内容的内部类型,您必须确保不违反任何容器的不变量。对于 Vec ,这意味着内部类型的大小和对齐方式都必须匹配。其他容器可能依赖于类型的大小、对齐方式,甚至是 TypeId ,在这种情况下,如果不违反容器不变量,就根本不可能进行转换。
let store = [0, 1, 2, 3];
let v_orig = store.iter().collect::>();
// clone the vector as we will reuse them later
let v_clone = v_orig.clone();
// Using transmute:this relies on the unspecified data layout of `Vec`, which is a
// bad idea and could cause Undefined Behavior.
// However, it is no-copy.
let v_transmuted = unsafe {
std::mem::transmute::, Vec
实施 split_at_mut :
use std::{slice, mem};
// There are multiple ways to do this, and there are multiple problems
// with the following (transmute) way.
fn split_at_mut_transmute(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);
// first:transmute is not type safe; all it checks is that T and
// U are of the same size. Second, right here, you have two
// mutable references pointing to the same memory.
(&mut slice[0..mid], &mut slice2[mid..len])
}
}
// This gets rid of the type safety problems; `&mut *` will *only* give
// you an `&mut T` from an `&mut T` or `*mut T`.
fn split_at_mut_casts(slice:&mut [T], mid:usize)
-> (&mut [T], &mut [T]) {
let len = slice.len();
assert!(mid <= len);
unsafe {
let slice2 = &mut *(slice as *mut [T]);
// however, you still have two mutable references pointing to
// the same memory.
(&mut slice[0..mid], &mut slice2[mid..len])
}
}
// This is how the standard library does it. This is the best method, if
// you need to do something like this
fn split_at_stdlib(slice:&mut [T], mid:usize)
-> (&mut [T], &mut [T]) {
let len = slice.len();
assert!(mid <= len);
unsafe {
let ptr = slice.as_mut_ptr();
// This now has three mutable references pointing at the same
// memory. `slice`, the rvalue ret.0, and the rvalue ret.1.
// `slice` is never used after `let ptr = ...`, and so one can
// treat it as "dead", and therefore, you only have two real
// mutable slices.
(slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid))
}
}
关于trait object:
use std::fmt::{Debug, Display};
use std::mem::transmute;
fn main() {
let s1 = String::from("hello world!");
let s2 = String::from("goodbye world!");
// Display / Debug trait object for s
let w1: &dyn Display = &s1;
let w2: &dyn Debug = &s1;
// Display / Debug trait object for s1
let w3: &dyn Display = &s2;
let w4: &dyn Debug = &s2;
// 强行把 triat object 转换成两个地址 (usize, usize)
// 这是不安全的,所以是 unsafe
let (addr1, vtable1): (usize, usize) = unsafe { transmute(w1) };
let (addr2, vtable2): (usize, usize) = unsafe { transmute(w2) };
let (addr3, vtable3): (usize, usize) = unsafe { transmute(w3) };
let (addr4, vtable4): (usize, usize) = unsafe { transmute(w4) };
// s 和 s1 在栈上的地址,以及 main 在 TEXT 段的地址
println!(
"s1: {:p}, s2: {:p}, main(): {:p}",
&s1, &s2, main as *const ()
);
// trait object(s / Display) 的 ptr 地址和 vtable 地址
println!("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1);
// trait object(s / Debug) 的 ptr 地址和 vtable 地址
println!("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2);
// trait object(s1 / Display) 的 ptr 地址和 vtable 地址
println!("addr3: 0x{:x}, vtable3: 0x{:x}", addr3, vtable3);
// trait object(s1 / Display) 的 ptr 地址和 vtable 地址
println!("addr4: 0x{:x}, vtable4: 0x{:x}", addr4, vtable4);
// 指向同一个数据的 trait object 其 ptr 地址相同
assert_eq!(addr1, addr2);
assert_eq!(addr3, addr4);
// 指向同一种类型的同一个 trait 的 vtable 地址相同
// 这里都是 String + Display
assert_eq!(vtable1, vtable3);
// 这里都是 String + Debug
assert_eq!(vtable2, vtable4);
}