🦀 Functional Rust

707: `std::mem::transmute` — Reinterpreting Bytes

Difficulty: 4 Level: Expert Reinterpret a value's bits as a different type — the nuclear option of unsafe Rust.

The Problem This Solves

Sometimes you genuinely need to look at the same bits through a different lens: inspect the IEEE-754 bit pattern of a float, reinterpret a `&str` fat pointer as `&[u8]`, or work with a C library that passes type-erased void pointers. Safe Rust has no way to do this directly — the type system prevents you from pretending a `f32` is a `u32`. `mem::transmute<T, U>` tells the compiler: "Take these bits, call them type `U` instead of type `T`, and don't generate any conversion code." It is the most dangerous function in the standard library. Misuse causes undefined behaviour immediately — not a panic, not a crash you can catch, but silent corruption or security vulnerabilities. Almost every use case has a safer, named alternative. Reach for `transmute` only after confirming no safer alternative exists. unsafe is a tool, not a crutch — use only when safe Rust genuinely can't express the pattern.

The Intuition

Transmute is bitwise reinterpretation. The bits in memory stay exactly the same; only the type label changes. The compiler enforces one compile-time rule: `size_of::<T>() == size_of::<U>()`. Everything else — alignment, validity invariants, aliasing rules, lifetime correctness — is your responsibility. The canonical safer alternatives: If a safe alternative exists, use it. The safe API documents intent, compiles to the same code, and doesn't require an `unsafe` block.

How It Works in Rust

use std::mem;

// ── Safe alternative is always preferred ────────────────────────────────
fn f32_to_u32_safe(f: f32) -> u32 { f.to_bits() }  // use this

// ── Transmute: same result, higher risk ─────────────────────────────────
fn f32_to_u32_transmute(f: f32) -> u32 {
 unsafe {
     // SAFETY: f32 and u32 both have size 4 and align 4 on all targets;
     // every bit pattern of u32 is a valid u32.
     mem::transmute::<f32, u32>(f)
 }
}

// ── Size mismatch is a compile-time error ────────────────────────────────
// let _: u32 = unsafe { mem::transmute::<u8, u32>(0u8) };  // ERROR

// ── Lifetime extension — extremely dangerous ────────────────────────────
/// # Safety
/// Caller must guarantee the referent is valid for the entire lifetime 'b.
unsafe fn extend_lifetime<'a, 'b, T>(r: &'a T) -> &'b T {
 // SAFETY: Caller contract — almost always a design smell.
 // Prefer arena allocators (bumpalo) or Rc/Arc instead.
 mem::transmute::<&'a T, &'b T>(r)
}
The size check is a compile-time guard: `const _: () = assert!(mem::size_of::<A>() == mem::size_of::<B>());`. Add this assertion next to any transmute to document the invariant explicitly.

What This Unlocks

Key Differences

ConceptOCamlRust
Bit reinterpretation`Obj.magic` (always unsafe, no size check)`mem::transmute` (compile error if sizes differ)
Float bitsNo built-in; need `Int32.bits_of_float` (Obj.magic)`f32::to_bits()` (safe) or `mem::transmute` (unsafe)
&str as bytes`Bytes.unsafe_of_string``.as_bytes()` (safe) or transmute (unsafe)
Lifetime extensionNot a typed conceptLifetime parameter transmute — extremely dangerous
Safety defaultNo safeguardsCompiler blocks mismatched sizes; auditor blocks the rest
//! 707 — std::mem::transmute: Reinterpreting Bytes
//! Safe-alternative-first approach; transmute only when necessary.

use std::mem;

// ── 1. Float ↔ integer bits (prefer safe API) ────────────────────────────

fn f32_to_u32_transmute(f: f32) -> u32 {
    unsafe {
        // SAFETY: f32 and u32 both have size 4 and align 4 on all targets;
        // every bit pattern of u32 is a valid u32 (no validity invariants).
        mem::transmute::<f32, u32>(f)
    }
}

fn f32_to_u32_safe(f: f32) -> u32 { f.to_bits() }  // prefer this

// ── 2. &str → &[u8] (prefer .as_bytes()) ─────────────────────────────────

fn str_as_bytes(s: &str) -> &[u8] {
    unsafe {
        // SAFETY: &str is layout-compatible with &[u8] (fat pointer: ptr + len).
        // The bytes of valid UTF-8 are valid u8 values.  Prefer s.as_bytes().
        mem::transmute::<&str, &[u8]>(s)
    }
}

// ── 3. Extending a lifetime (VERY DANGEROUS — shown for pedagogy) ─────────

/// Extend the lifetime of a reference.
/// Almost always a design smell — prefer arena allocators or Rc.
///
/// # Safety
/// Caller must guarantee the referent is valid for the entire lifetime 'b.
#[allow(dead_code)]
unsafe fn extend_lifetime<'a, 'b, T>(r: &'a T) -> &'b T {
    // SAFETY: Caller contract documented above.
    mem::transmute::<&'a T, &'b T>(r)
}

// ── Compile-time size assertion ───────────────────────────────────────────
const _: () = assert!(mem::size_of::<f32>() == mem::size_of::<u32>());

fn main() {
    let pi: f32 = std::f32::consts::PI;
    let t = f32_to_u32_transmute(pi);
    let s = f32_to_u32_safe(pi);
    println!("transmute:  {:#010x}", t);
    println!("to_bits:    {:#010x}", s);
    println!("Equal: {}", t == s);

    let text = "hello, Rust!";
    let via_transmute = str_as_bytes(text);
    let via_safe      = text.as_bytes();
    println!("Bytes match: {}", via_transmute == via_safe);

    // Size mismatch would be a COMPILE ERROR:
    // let _: u32 = unsafe { mem::transmute::<u8, u32>(0u8) }; // error
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_float_transmute_matches_to_bits() {
        for &f in &[0.0f32, 1.0, -1.0, f32::INFINITY, 1.234] {
            assert_eq!(f32_to_u32_transmute(f), f32_to_u32_safe(f));
        }
    }

    #[test]
    fn test_str_bytes() {
        let s = "abcde";
        assert_eq!(str_as_bytes(s), b"abcde");
    }

    #[test]
    fn test_nan_transmute() {
        let nan = f32::NAN;
        assert_eq!(f32_to_u32_transmute(nan), f32_to_u32_safe(nan));
    }
}
(* OCaml: Obj.magic is the transmute equivalent — equally dangerous.
   Always prefer typed conversions. *)

(** Float to bits — idiomatic safe way. *)
let float_to_bits (f : float) : int64 = Int64.bits_of_float f
let bits_to_float (b : int64) : float = Int64.float_of_bits b

(** int32 byte view via Bytes — safe byte manipulation. *)
let int32_to_bytes_le (n : int32) : bytes =
  let b = Bytes.create 4 in
  Bytes.set_int32_le b 0 n;
  b

let () =
  let pi = Float.pi in
  Printf.printf "pi bits: 0x%Lx\n" (float_to_bits pi);
  Printf.printf "round-trip: %f (equal: %b)\n"
    (bits_to_float (float_to_bits pi))
    (pi = bits_to_float (float_to_bits pi));
  let bytes = int32_to_bytes_le 0x12345678l in
  print_string "int32 LE bytes: ";
  Bytes.iter (fun c -> Printf.printf "%02x " (Char.code c)) bytes;
  print_newline ()