Rust std::mem::transmute实例讲解

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>>(v_clone)
};

let v_clone = v_orig.clone();

// This is the suggested, safe way.
// It does copy the entire vector, though, into a new array.
let v_collected = v_clone.into_iter()
                         .map(Some)
                         .collect::>>();

let v_clone = v_orig.clone();

// This is the proper no-copy, unsafe way of "transmuting" a `Vec`, without relying on the
// data layout. Instead of literally calling `transmute`, we perform a pointer cast, but
// in terms of converting the original inner type (`&i32`) to the new one (`Option<&i32>`),
// this has all the same caveats. Besides the information provided above, also consult the
// [`from_raw_parts`] documentation.
let v_from_raw = unsafe {
    // Ensure the original vector is not dropped.
    let mut v_clone = std::mem::ManuallyDrop::new(v_clone);
    Vec::from_raw_parts(v_clone.as_mut_ptr() as *mut Option<&i32>,
                        v_clone.len(),
                        v_clone.capacity())
};

实施 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);
}

你可能感兴趣的:(Rust,rust,开发语言,后端)