Function core::intrinsics::transmute

1.0.0 (const: 1.56.0) · source ·
pub const unsafe extern "rust-intrinsic" fn transmute<Src, Dst>(
    src: Src
) -> Dst
Expand description

将一种类型的值的位重新解释为另一种类型。

两种类型都必须具有相同的大小。如果不能保证,编译将失败。

transmute 在语义上等同于将一种类型按位移动到另一种类型。它将位从源值复制到目标值,然后忘记原始值。 请注意,源和目标是按值传递的,这意味着如果 SrcDst 包含填充,则保证 transmute 保留该填充。

参数和结果都必须是给定类型的 valid。违反此条件会导致 未定义的行为。 编译器将生成代码 *assuming,您 (程序员) 确保永远不会出现未定义的行为 *。 因此,您有责任保证传递给 transmute 的每个值在 SrcDst 类型中都有效。 不遵守此条件可能会导致意外和不稳定的编译结果。 这使得 transmute 非常不安全transmute 应该是绝对不得已的方法。

const 上下文中转换指向整数的指针是 未定义的行为。 任何将结果值用于整数运算的尝试都将中止常量计算。 (即使在 const 之外,这种转换也涉及到 Rust 内存模型的许多未指定方面,应该避免。请参见下面的替代方案。)

由于 transmute 是按值运算,因此不必担心 transmuted values 本身的对齐。 与任何其他函数一样,编译器已经确保 SrcDst 正确对齐。 但是,当将 point 的值转换为其他位置(例如指针,引用,boxes…) 时,调用者必须确保所指向的值正确对齐。

nomicon 具有其他文档。

Examples

transmute 确实有一些用途。

将指针转换为函数指针。对于函数指针和数据指针具有不同大小的机器,这不是可移植的。

fn foo() -> i32 {
    0
}
// 至关重要的是,我们在转换为函数指针之前 `as`-cast 为一个裸指针。
// 这避免了整数到指针 `transmute`,这可能是有问题的。
// 在裸指针 (即两个指针类型) 之间转换是可以的。
let pointer = foo as *const ();
let function = unsafe {
    std::mem::transmute::<*const (), fn() -> i32>(pointer)
};
assert_eq!(function(), 0);
Run

延长生命周期或缩短不变的生命周期。这是高级的,非常不安全的 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)
}
Run

Alternatives

不要失望: transmute 的许多用途可以通过其他方式实现。 以下是 transmute 的常见应用程序,可以用更安全的结构替换它。

将原始字节 ([u8; SZ]) 转换为 u32f64 等:

let raw_bytes = [0x78, 0x56, 0x34, 0x12];

let num = unsafe {
    std::mem::transmute::<[u8; 4], u32>(raw_bytes)
};

// 请改用 `u32::from_ne_bytes`
let num = u32::from_ne_bytes(raw_bytes);
// 或使用 `u32::from_le_bytes` 或 `u32::from_be_bytes` 指定字节顺序
let num = u32::from_le_bytes(raw_bytes);
assert_eq!(num, 0x12345678);
let num = u32::from_be_bytes(raw_bytes);
assert_eq!(num, 0x78563412);
Run

将指针变成 usize

let ptr = &0;
let ptr_num_transmute = unsafe {
    std::mem::transmute::<&i32, usize>(ptr)
};

// 请改用 `as` cast
let ptr_num_cast = ptr as *const i32 as usize;
Run

请注意,使用 transmute 将指针转换为 usize 是(如上所述)在 const 上下文中的 未定义行为。 同样在 consts 之外,这个操作可能不会像预期的那样运行 – 这涉及 Rust 内存模型的许多未指定方面。 根据代码的作用,以下替代方法比指针到整数的转换更可取:

  • 如果代码只是想在某个缓冲区中存储任意类型的数据并且需要为该缓冲区选择一种类型,则可以使用 MaybeUninit
  • 如果代码确实想处理指针指向的地址,它可以使用 as 强制转换或 ptr.addr()

*mut T 变成 &mut T

let ptr: *mut i32 = &mut 0;
let ref_transmuted = unsafe {
    std::mem::transmute::<*mut i32, &mut i32>(ptr)
};

// 请改用 reborrow
let ref_casted = unsafe { &mut *ptr };
Run

&mut T 变成 &mut U

let ptr = &mut 0;
let val_transmuted = unsafe {
    std::mem::transmute::<&mut i32, &mut u32>(ptr)
};

// 现在,将 `as` 和 reborrowing 放在一起 - 请注意,`as` `as` 的链接是不可传递的
let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };
Run

&str 变成 &[u8]

// 这不是执行此操作的好方法。
let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") };
assert_eq!(slice, &[82, 117, 115, 116]);

// 您可以使用 `str::as_bytes`
let slice = "Rust".as_bytes();
assert_eq!(slice, &[82, 117, 115, 116]);

// 或者,如果您可以控制字符串,则只需使用字节字符串即可。
assert_eq!(b"Rust", &[82, 117, 115, 116]);
Run

Vec<&T> 变成 Vec<Option<&T>>

要转换容器内容的内部类型,必须确保不违反容器的任何不变量。 对于 Vec,这意味着内部类型的大小和对齐方式都必须匹配。 其他容器可能依赖于类型,对齐方式甚至 TypeId 的大小,在这种情况下,在不违反容器不变量的情况下根本不可能进行转换。

let store = [0, 1, 2, 3];
let v_orig = store.iter().collect::<Vec<&i32>>();

// 克隆 vector,因为稍后我们将重用它们
let v_clone = v_orig.clone();

// 使用 transmute: 这依赖于 `Vec` 的未指定数据布局,这是一个坏主意,并可能导致未定义的行为。
// 但是,它不是 copy 的。
let v_transmuted = unsafe {
    std::mem::transmute::<Vec<&i32>, Vec<Option<&i32>>>(v_clone)
};

let v_clone = v_orig.clone();

// 这是建议的安全方法。
// 但是,它确实将整个 vector 复制到一个新数组中。
let v_collected = v_clone.into_iter()
                         .map(Some)
                         .collect::<Vec<Option<&i32>>>();

let v_clone = v_orig.clone();

// 这是 "transmuting" 和 `Vec` 的正确无复制,不安全的方式,而无需依赖数据布局。
// 我们不执行字面上的调用 `transmute`,而是执行指针强制转换,但是就将原始内部类型 (`&i32`) 转换为新的 (`Option<&i32>`) 而言,这具有所有相同的警告。
// 除了上面提供的信息之外,还请查阅 [`from_raw_parts`] 文档。
let v_from_raw = unsafe {
    // 确保原始 vector 没有被丢弃。
    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())
};
Run

实现 split_at_mut

use std::{slice, mem};

// 有多种方法可以执行此操作,并且以下 (transmute) 方法存在多个问题。
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);
        // 第一:transmute 不是类型安全的; 它只检查 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`,右值 ret.0 和右值 ret.1。
        // `slice` 在 `let ptr = ...` 之后就不再使用了,所以可以把它当作 "dead" 来对待,所以,您只有两个真正的可变切片。
        (slice::from_raw_parts_mut(ptr, mid),
         slice::from_raw_parts_mut(ptr.add(mid), len - mid))
    }
}
Run