🎓 A1Learner
🔍

Rust Transmute

std::mem::transmute is a function that reads one value's bits as another type. The two types must have the same size.

Same bits, new type

transmute reinterprets the bits of a value as another type. Nothing is converted — the bits are untouched:

let bits: u32 = 2.5f32.to_bits();
let back: f32 = unsafe { std::mem::transmute(bits) };
println!("{back}");

The size must match exactly

Both types must have exactly the same size, checked while compiling. This line never builds — it fails with error E0512:

let x: u64 = unsafe { std::mem::transmute(7u32) }; // error E0512: sizes differ

Bytes in, value out

A [u8; 4] and a u32 have the same size. So some bytes can be viewed as one number. They may arrive from a file. They may arrive from the network:

let bytes: [u8; 4] = bits.to_ne_bytes();
let joined: u32 = unsafe { std::mem::transmute(bytes) };

Floats have a safe door

For floats, skip transmute entirely — from_bits does the same reinterpretation with no unsafe at all:

let safe_back = f32::from_bits(bits);
println!("{safe_back}");

Sound only because 65 is a real character

A u32 and a char are both 4 bytes. But this is sound for one reason only. 65 is a valid Unicode value (A). An invalid pattern would be instant undefined behaviour:

let letter: char = unsafe { std::mem::transmute(65u32) };
println!("{letter}");

Example

fn main() {
    let number: f32 = 2.5;
    let bits: u32 = number.to_bits();
    println!("2.5 as bits = {bits}");
    let back: f32 = unsafe { std::mem::transmute(bits) };
    println!("bits back as float = {back}");
    let bytes: [u8; 4] = bits.to_ne_bytes();
    let joined: u32 = unsafe { std::mem::transmute(bytes) };
    println!("rebuilt = {joined}");
    let letter: char = unsafe { std::mem::transmute(65u32) };
    println!("65 as char = {letter}");
}

Key points

  • transmute is an unsafe fn: the compiler checks only the sizes, never whether the bits form a valid To
  • An invalid bit pattern is instant undefined behaviour.
  • A bool must be 0 or 1.
  • A char must be a real Unicode value.
  • A reference must be non-null.
  • It must be aligned
  • A union and transmute do the same reinterpretation.
  • The union names the shared memory once.
  • transmute does it in a single call.
  • Transmute is sometimes the only tool that fits the job.
  • Transmute reinterprets one value's bits as another type of the same size.

In this example, 2.5 is turned into its bit pattern first. The safe to_bits does that. The pattern is 1075838976. The Unions lesson read the same number. It read it out of shared memory. transmute then views those bits as an f32 again, giving back exactly 2.5: no conversion happened in either direction. Next the bits become four bytes with to_ne_bytes, and a second transmute views the bytes as one u32, rebuilding 1075838976. Last, the u32 value 65 becomes a char — sound here only because 65 is a valid Unicode value (A). Try this program yourself. rustc will warn about some of these transmutes. It will call them unnecessary. It suggests from_bits, from_ne_bytes and char::from_u32. In real code, take the hint. Use the safe method.

Note

Both are unsafe for the same reason: only you know the bits are valid.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does transmute::<u32, u64>(x) do?