The Most Horribly Unsafe Thing You Can Do in Rust
Table of Contents
mem::transmute reinterprets raw bytes across types with almost no checks. Here's why it's more dangerous than raw pointers, and the layout traps that make it worse.
Not raw pointers. Not manual memory management. Not even unsafe blocks.
mem::transmute.
It takes a value of type T and reinterprets the raw bytes as type U. The compiler checks one thing: that T and U are the same size. That’s it. No validation, no conversion, nothing.
What It Actually Does
pub const unsafe fn transmute<Src, Dst>(src: Src) -> Dst
You can transmute a u64 into a reference. A u8 into a bool that’s neither true nor false. And the compiler will trust that impossible value for every optimization it makes from that point on.
Overloaded Return Type
The return type is overloaded — forget to annotate it and the compiler just infers whatever makes the surrounding code compile. Wrong type, no warning, have fun debugging that.
// What type does this return? Whatever the compiler decides.
let x = unsafe { std::mem::transmute(some_value) };&T to &mut T is Always UB
Transmuting &T to &mut T? Always undefined behavior. Doesn’t matter if “nobody else is reading it”. The optimizer assumes shared references never change. Break that and your code works today, segfaults on the next compiler version.
// Do NOT do this. Ever.
let shared: &u32 = &42;
let mutable: &mut u32 = unsafe { std::mem::transmute(shared) };
Creating the &mut is already UB — not just mutating through it. The aliasing violation happens the moment the mutable reference exists alongside the shared one. The compiler is free to cache reads through &T, eliminate redundant loads, reorder operations — all based on the assumption that the referent won’t change. Every optimization built on that assumption is now wrong.
The Layout Trap
The nastiest part. Default repr(Rust) gives zero guarantees on field ordering. The compiler reorders struct fields however it wants. Two structs with the same fields in different order — or even the same generic struct with different type parameters like Wrapper<u8> vs Wrapper<u64> — can have completely different memory layouts. Transmute between them and you get garbage out.
Only repr(C) and repr(transparent) give you a stable, defined layout. Everything else is the compiler’s playground.
transmute_copy
mem::transmute_copy drops even the size check. It copies size_of::<U> bytes out of an &T and interprets them as U. Target type is bigger than source? It just reads past your value into whatever sits next in memory.
The Bottom Line
Every transmute is a bet that you understand memory layout better than the compiler does. Most people lose that bet.
You can get the same functionality with raw pointer casts or unions — but they’re actually worse. Transmute at least gives you lints and a compile-time size check. Pointer casts and unions give you nothing. And neither lets you bypass the actual rules — the rules exist whether the compiler enforces them or not.