Safe Transmute Patterns
Tutorial Video
Text description (accessibility)
This video demonstrates the "Safe Transmute Patterns" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. `std::mem::transmute` is one of Rust's most dangerous functions — it reinterprets the bits of a value as a completely different type with no checks. Key difference from OCaml: 1. **Safety surface**: Rust's `transmute` requires explicit `unsafe` and size matching; OCaml's `Obj.magic` is less visible and has fewer compile
Tutorial
The Problem
std::mem::transmute is one of Rust's most dangerous functions — it reinterprets the bits of a value as a completely different type with no checks. Misuse causes undefined behavior, security vulnerabilities, and data corruption. But the need behind transmute is legitimate: zero-copy conversion between types with the same memory representation, view casting byte slices as structured data, and FFI type bridging. Safe alternatives exist for most use cases: from_utf8, from_le_bytes, bytemuck::cast, and the zerocopy crate provide the same functionality with compile-time safety checks.
🎯 Learning Outcomes
transmute is dangerous: no alignment, size, or validity checksstd::str::from_utf8(bytes) safely converts byte slices to &strT::from_le_bytes / to_le_bytes safely converts between numeric types and byte arraysbytemuck pattern works: as_bytes<T: Copy>(val: &T) -> &[u8] with careful unsafeCode Example
#![allow(clippy::all)]
//! Safe Transmute Patterns
//!
//! Converting between types with same representation.
/// Safe byte slice to str (with validation).
pub fn bytes_to_str(bytes: &[u8]) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(bytes)
}
/// Safe conversion via From/Into.
pub fn convert_safe() {
let n: u32 = 42;
let bytes = n.to_le_bytes();
let back = u32::from_le_bytes(bytes);
assert_eq!(n, back);
}
/// Transmute alternative: use bytemuck-style.
pub fn as_bytes<T: Copy>(val: &T) -> &[u8] {
unsafe { std::slice::from_raw_parts(val as *const T as *const u8, std::mem::size_of::<T>()) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bytes_to_str() {
let bytes = b"hello";
assert_eq!(bytes_to_str(bytes).unwrap(), "hello");
}
#[test]
fn test_bytes_to_str_invalid() {
let bytes = &[0xFF, 0xFE];
assert!(bytes_to_str(bytes).is_err());
}
#[test]
fn test_convert_safe() {
convert_safe(); // just verify it runs
}
#[test]
fn test_as_bytes() {
let n: u32 = 0x12345678;
let bytes = as_bytes(&n);
assert_eq!(bytes.len(), 4);
}
}Key Differences
transmute requires explicit unsafe and size matching; OCaml's Obj.magic is less visible and has fewer compile-time guards.from_utf8, from_le_bytes, bytemuck as safe transmute alternatives with wide stdlib and crate coverage; OCaml's Bytes module provides similar safe APIs.std::mem::TransmuteFrom safe trait that encodes layout compatibility at the type level; OCaml has no equivalent roadmap.#[repr(C)] structs can be safely cast to byte slices with bytemuck; OCaml uses ctypes for FFI type bridging.OCaml Approach
OCaml's Bytes.get_uint8, String.get_uint16_le, and Bigarray provide safe byte-level access. Obj.magic is OCaml's equivalent of transmute — equally dangerous and discouraged:
let bytes_to_string b = Bytes.to_string b (* safe copy *)
let u32_of_bytes b pos = Bytes.get_int32_le b pos |> Int32.to_int (* safe *)
Full Source
#![allow(clippy::all)]
//! Safe Transmute Patterns
//!
//! Converting between types with same representation.
/// Safe byte slice to str (with validation).
pub fn bytes_to_str(bytes: &[u8]) -> Result<&str, std::str::Utf8Error> {
std::str::from_utf8(bytes)
}
/// Safe conversion via From/Into.
pub fn convert_safe() {
let n: u32 = 42;
let bytes = n.to_le_bytes();
let back = u32::from_le_bytes(bytes);
assert_eq!(n, back);
}
/// Transmute alternative: use bytemuck-style.
pub fn as_bytes<T: Copy>(val: &T) -> &[u8] {
unsafe { std::slice::from_raw_parts(val as *const T as *const u8, std::mem::size_of::<T>()) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bytes_to_str() {
let bytes = b"hello";
assert_eq!(bytes_to_str(bytes).unwrap(), "hello");
}
#[test]
fn test_bytes_to_str_invalid() {
let bytes = &[0xFF, 0xFE];
assert!(bytes_to_str(bytes).is_err());
}
#[test]
fn test_convert_safe() {
convert_safe(); // just verify it runs
}
#[test]
fn test_as_bytes() {
let n: u32 = 0x12345678;
let bytes = as_bytes(&n);
assert_eq!(bytes.len(), 4);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bytes_to_str() {
let bytes = b"hello";
assert_eq!(bytes_to_str(bytes).unwrap(), "hello");
}
#[test]
fn test_bytes_to_str_invalid() {
let bytes = &[0xFF, 0xFE];
assert!(bytes_to_str(bytes).is_err());
}
#[test]
fn test_convert_safe() {
convert_safe(); // just verify it runs
}
#[test]
fn test_as_bytes() {
let n: u32 = 0x12345678;
let bytes = as_bytes(&n);
assert_eq!(bytes.len(), 4);
}
}
Deep Comparison
OCaml vs Rust: lifetime transmute safe
See example.rs and example.ml for implementations.
Key Differences
Exercises
fn parse_ipv4_header(bytes: &[u8]) -> Option<(u8, u8, u16)> that reads version, TTL, and total length from a byte slice without transmute.fn rgba_to_u32(r: u8, g: u8, b: u8, a: u8) -> u32 using bit shifting and fn u32_to_rgba(v: u32) -> (u8, u8, u8, u8) — no unsafe needed.bytemuck::Pod (or implement the byte-view pattern manually) to view a #[repr(C)] struct Point { x: f32, y: f32 } as &[u8] safely.