Loading page…
Rust walkthroughs
Loading page…
mem::transmute<T, U> reinterprets the bits of a value of type T as type U. It's one of the most powerful and dangerous operations in Rust, completely bypassing the type system. Both types must have the same size.
Key concepts:
When transmute is appropriate:
Safer alternatives to prefer:
as for numeric conversionsFrom/Into for type conversionstry_from/try_into for fallible conversionstransmute_copy when sizes differbytemuck crate for safe transmutationszerocopy crate for safe reinterpretationuse std::mem;
fn main() {
// Transmute between same-sized types
let bytes: [u8; 4] = [0x12, 0x34, 0x56, 0x78];
// Reinterpret bytes as u32 (little-endian systems)
let number: u32 = unsafe { mem::transmute(bytes) };
println!("Bytes {:?} as u32: {}", bytes, number);
// Transmute back
let back_to_bytes: [u8; 4] = unsafe { mem::transmute(number) };
println!("u32 {} back to bytes: {:?}", number, back_to_bytes);
}fn main() {
// Instead of transmute for numeric types, use as:
let a: i32 = 42;
let b: u32 = a as u32; // Safe, defined behavior
// Or use From/Into for known-safe conversions
let c: u32 = i32::from_ne_bytes([0x12, 0x34, 0x56, 0x78]);
let d: [u8; 4] = c.to_ne_bytes();
println!("i32: {}, u32: {}, bytes: {:?}", a, b, d);
}use std::mem;
fn main() {
// Transmute f64 to u64 (same size)
let float_val: f64 = 3.14159;
let bits: u64 = unsafe { mem::transmute(float_val) };
println!("f64 {} as bits: {:#016x}", float_val, bits);
// Transmute back
let back: f64 = unsafe { mem::transmute(bits) };
println!("Bits back to f64: {}", back);
// Safer alternative: use to_bits/from_bits
let safe_bits = float_val.to_bits();
let safe_back = f64::from_bits(safe_bits);
println!("Safe conversion: {}", safe_back);
}use std::mem;
fn main() {
// Transmute &T to &U (same layout required!)
let slice: &[u8] = &[0x01, 0x02, 0x03, 0x04];
// WARNING: This is only safe if the layouts are identical!
// &[u8; 4] and &[u8] have DIFFERENT layouts - this would be UB!
// Safe example: &[u8; 4] to &[u32; 1] (both are 4 bytes + metadata)
let arr: [u8; 4] = [0x01, 0x02, 0x03, 0x04];
let arr_ref: &[u8; 4] = &arr;
// This transmutes the reference, not the data
// DANGEROUS: alignment and endianness matter!
// unsafe {
// let u32_ref: &[u32; 1] = mem::transmute(arr_ref);
// }
// Safer approach: use try_into or explicit conversion
let u32_val = u32::from_ne_bytes(*arr_ref);
println!("Converted: {}", u32_val);
}use std::mem;
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
fn main() {
// Function pointer types
type BinaryOp = fn(i32, i32) -> i32;
let op: BinaryOp = add;
println!("add(2, 3) = {}", op(2, 3));
// Transmuting function pointers is dangerous but sometimes needed for FFI
// The ABI must be compatible!
// Example: Converting to a "generic" function pointer
type GenericFn = unsafe extern "C" fn();
// WARNING: Calling through this requires knowing the actual signature
// unsafe {
// let generic: GenericFn = mem::transmute(op);
// // Must transmute back to correct type to call safely
// }
}use std::mem;
use std::ffi::c_void;
// FFI structures with overlapping layouts
#[repr(C)]
struct sockaddr_in {
sin_family: u16,
sin_port: u16,
sin_addr: u32,
sin_zero: [u8; 8],
}
#[repr(C)]
struct sockaddr {
sa_family: u16,
sa_data: [u8; 14],
}
// FFI often requires casting between related types
// In C: (struct sockaddr *)&addr_in
// In Rust: need to ensure layouts match!
fn main() {
let addr_in = sockaddr_in {
sin_family: 2,
sin_port: 8080u16.to_be(),
sin_addr: 0x7f000001, // 127.0.0.1
sin_zero: [0; 8],
};
// Both structs are 16 bytes
println!("sockaddr_in size: {}", mem::size_of::<sockaddr_in>());
println!("sockaddr size: {}", mem::size_of::<sockaddr>());
// For FFI, this transmute is sometimes necessary
// unsafe {
// let addr: sockaddr = mem::transmute(addr_in);
// }
// Safer: use a union or explicit conversion
}use std::mem;
fn main() {
// transmute requires same size
// transmute_copy copies bytes to a new value
let small: [u8; 2] = [0x12, 0x34];
let large: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
// Can't use transmute - sizes differ!
// let x: [u8; 4] = unsafe { mem::transmute(small) }; // Won't compile
// transmute_copy reads size_of::<U>() bytes
// BE CAREFUL: reads beyond source if U is larger!
let source = [0x12u8, 0x34, 0x56, 0x78];
let dest: u32 = unsafe { mem::transmute_copy(&source) };
println!("transmute_copy result: {}", dest);
}use std::mem;
fn main() {
// Convert slice of one type to slice of another
// This requires careful size and alignment handling!
let bytes: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
// Convert &[u8; 8] to &[u32; 2] (both 8 bytes)
// But requires proper alignment!
// Using from_raw_parts for slice conversion
let u32_slice: &[u32] = unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const u32,
bytes.len() / 4,
)
};
// Note: This may cause UB if alignment is wrong!
// Prefer safe alternatives when possible
println!("u32 slice len: {}", u32_slice.len());
}// Add to Cargo.toml: bytemuck = "1.0"
fn main() {
// bytemuck provides safe transmute for Pod (Plain Old Data) types
use bytemuck::{Pod, Zeroable, cast, try_cast};
// Safe transmute using bytemuck
let bytes: [u8; 4] = [0x12, 0x34, 0x56, 0x78];
// bytemuck::cast is safe for Pod types
let number: u32 = cast(bytes);
println!("bytemuck cast: {}", number);
// Can also try_cast which returns Result
match try_cast::<[u8; 4], u32>(bytes) {
Ok(n) => println!("Success: {}", n),
Err(_) => println!("Cast failed"),
}
}use std::mem;
trait Animal {
fn speak(&self);
}
struct Dog;
impl Animal for Dog {
fn speak(&self) {
println!("Woof!");
}
}
struct Cat;
impl Animal for Cat {
fn speak(&self) {
println!("Meow!");
}
}
fn main() {
// Trait object representation: (data_ptr, vtable_ptr)
let dog: Box<dyn Animal> = Box::new(Dog);
// The trait object is a fat pointer (2 pointers)
println!("Size of &dyn Animal: {}", mem::size_of::<&dyn Animal>());
println!("Size of (usize, usize): {}", mem::size_of::<(usize, usize)>());
// Transmuting trait objects is extremely dangerous!
// The vtable layout is not stable
// Safe pattern: use Any for downcasting
use std::any::Any;
let erased: Box<dyn Any> = Box::new(Dog);
if let Some(dog) = erased.downcast_ref::<Dog>() {
dog.speak();
}
}use std::mem;
// WARNING: Transmuting lifetimes is almost always wrong!
// This creates dangling references!
// UNSOUND EXAMPLE (do not use):
// fn extend_lifetime<'a, 'b>(s: &'a str) -> &'b str {
// unsafe { mem::transmute::<&'a str, &'b str>(s) }
// }
// This would let you use a reference after its referent is dropped:
// fn bad() {
// let s = String::from("hello");
// let extended = extend_lifetime(&s);
// drop(s); // s is dropped
// println!("{}", extended); // Use after free!
// }
fn main() {
println!("Transmuting lifetimes is unsound!");
println!("Use covariance or lifetime bounds instead.");
}use std::mem;
#[derive(Clone, Copy)]
#[repr(C)]
struct Color {
r: u8,
g: u8,
b: u8,
a: u8,
}
impl Color {
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
fn as_u32(&self) -> u32 {
// Safe conversion for #[repr(C)] types
u32::from_ne_bytes([self.r, self.g, self.b, self.a])
}
fn from_u32(value: u32) -> Self {
let [r, g, b, a] = value.to_ne_bytes();
Self { r, g, b, a }
}
}
fn main() {
let color = Color::new(255, 128, 64, 255);
println!("Color as u32: {}", color.as_u32());
let back = Color::from_u32(color.as_u32());
println!("Reconstructed: r={}, g={}, b={}, a={}", back.r, back.g, back.b, back.a);
}use std::mem;
#[repr(transparent)]
struct Wrapper<T>(T);
impl<T> Wrapper<T> {
fn new(inner: T) -> Self {
Wrapper(inner)
}
fn into_inner(self) -> T {
self.0
}
// For #[repr(transparent)] types, transmute is actually safe!
// But prefer using the inner value directly or as_ref/as_mut
}
fn main() {
let wrapped = Wrapper::new(42i32);
// repr(transparent) guarantees same layout
println!("Wrapper<i32> size: {}", mem::size_of::<Wrapper<i32>>());
println!("i32 size: {}", mem::size_of::<i32>());
// Safe: repr(transparent) guarantees identical layout
let inner: i32 = unsafe { mem::transmute(wrapped) };
println!("Transmuted: {}", inner);
}use std::mem;
fn main() {
// MISTAKE 1: Different sizes
// let x: u32 = unsafe { mem::transmute(0u8) }; // Won't compile!
// MISTAKE 2: Ignoring alignment
// let bytes: [u8; 4] = [1, 2, 3, 4];
// let ptr = bytes.as_ptr() as *const u32;
// let val = unsafe { *ptr }; // UB if not aligned!
// MISTAKE 3: Transmuting &T to &mut T (UB!)
// let x = 42;
// let ref_x = &x;
// let mut_x: &mut i32 = unsafe { mem::transmute(ref_x) }; // UB!
// MISTAKE 4: Transmuting away Send/Sync
// struct NotSync(std::cell::Cell<i32>);
// let sync: std::sync::Mutex<NotSync> = /* ... */;
// let unsync = unsafe { mem::transmute(sync) }; // UB when used!
println!("Avoid these common transmute mistakes!");
// CORRECT APPROACH: Use safe abstractions
let bytes = [1u8, 2, 3, 4];
let val = u32::from_ne_bytes(bytes); // Safe!
println!("Safe conversion: {}", val);
}use std::mem;
fn main() {
// Check sizes at compile time
assert_eq!(mem::size_of::<u32>(), mem::size_of::<[u8; 4]>());
// Check alignment
println!("u32 alignment: {}", mem::align_of::<u32>());
println!("[u8; 4] alignment: {}", mem::align_of::<[u8; 4]>());
// Use transmute_copy carefully if sizes differ
let source: u16 = 0x1234;
let dest: u32 = unsafe { mem::transmute_copy(&source) };
// Warning: reads 4 bytes from 2-byte source! UB!
// Better: explicit zero-extension
let safe_dest: u32 = source as u32;
println!("Safe result: {}", safe_dest);
}transmute Requirements:
| Requirement | Enforcement | |-------------|-------------| | Same size | Compile-time (must be equal) | | Same alignment | Not enforced (can cause UB) | | Valid bit pattern | Not enforced (can cause UB) | | Valid lifetime | Not enforced (UB to violate) | | Maintains safety invariants | Not enforced (UB to violate) |
transmute vs Alternatives:
| Method | Safety | Use Case |
|--------|--------|----------|
| as | Safe | Numeric casts, pointer casts |
| From/Into | Safe | Known-safe type conversions |
| try_from/try_into | Safe | Fallible type conversions |
| to_ne_bytes/from_ne_bytes | Safe | Numeric to bytes |
| transmute | Unsafe | Bit reinterpretation |
| transmute_copy | Unsafe | Copy bits to different size |
| bytemuck::cast | Safe | Pod type transmutation |
When transmute is Safe:
| Scenario | Safety |
|----------|--------|
| #[repr(transparent)] types | ✅ Generally safe |
| #[repr(C)] same-layout structs | ⚠️ Check alignment |
| Numeric types to bytes | ✅ Use to_ne_bytes instead |
| Bytes to numeric | ⚠️ Use from_ne_bytes instead |
| &T to &mut T | ❌ Always UB |
| Lifetime extension | ❌ Always UB |
| Different sizes | ❌ Won't compile |
Key Points:
transmute reinterprets bits as another type of the same sizebytemuck or zerocopy crates for safe transmutationtransmute_copy allows different sizes but reads arbitrary memory&T to &mut T (undefined behavior)repr(C) or repr(transparent) when transmute is necessaryPod (Plain Old Data) types for safe bit manipulation