๐Ÿฆ€ Functional Rust
๐ŸŽฌ Rust Ownership in 30 seconds Visual walkthrough of ownership, moves, and automatic memory management.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Each value in Rust has exactly one owner โ€” when the owner goes out of scope, the value is dropped

โ€ข Assignment moves ownership by default; the original binding becomes invalid

โ€ข Borrowing (&T / &mut T) lets you reference data without taking ownership

โ€ข The compiler enforces: many shared references OR one mutable reference, never both

โ€ข No garbage collector needed โ€” memory is freed deterministically at scope exit

554: Safe Transmute with Lifetimes

Difficulty: 5 Level: Advanced When and how to reason about `std::mem::transmute` on lifetime parameters โ€” and why the safe alternatives almost always suffice.

The Problem This Solves

Rust's lifetime system prevents dangling references at compile time. But occasionally โ€” in allocators, arena-based data structures, FFI wrappers, and self-referential patterns โ€” you have a reference whose validity you can prove but the compiler cannot. The instinct is to reach for `transmute` to change the lifetime annotation and silence the borrow checker. This is extremely dangerous. A transmuted lifetime tells the compiler "trust me, this reference lives long enough." If you are wrong, the result is a use-after-free โ€” undefined behaviour that the borrow checker can no longer catch. Understanding why it is dangerous, when it might be justified, and what safe alternatives exist is essential for writing low-level Rust. The key insight is that in almost every real-world case, there is a safe alternative: `from_raw_parts` with documented SAFETY invariants, `Pin` for self-referential structs, `Arc` for shared ownership, or simply restructuring ownership to let the borrow checker see the relationship.

The Intuition

`transmute<T, U>(x: T) -> U` reinterprets the bits of `x` as type `U` with zero machine code. When `T = &'short Foo` and `U = &'long Foo`, the bits are identical (it's just a pointer), but the lifetime annotation changes. The pointer itself is not affected โ€” only the compiler's bookkeeping is. The danger: the compiler now assumes the reference is valid for `'long`. If the underlying data is dropped before `'long` ends, you get a dangling pointer. The borrow checker trusted you and stopped watching. Safe patterns express the same relationship through proper API: `slice::from_raw_parts` (SAFETY comment required), `from_raw_parts_mut`, or by returning `&self.field` (where the compiler infers the lifetime from `self`).

How It Works in Rust

The dangerous pattern โ€” do not use without proven invariants:
unsafe fn extend_lifetime_unsafe<'short, 'long, T>(r: &'short T) -> &'long T {
 std::mem::transmute(r)
}
Safe alternative โ€” split_at with documented SAFETY:
fn safe_split(data: &[i32], mid: usize) -> (&[i32], &[i32]) {
 assert!(mid <= data.len());
 unsafe {
     let ptr = data.as_ptr();
     // SAFETY: ptr..ptr+mid and ptr+mid..ptr+len are non-overlapping
     // slices within the same allocation, valid for lifetime of `data`
     let left  = std::slice::from_raw_parts(ptr, mid);
     let right = std::slice::from_raw_parts(ptr.add(mid), data.len() - mid);
     (left, right)
 }
}
This is exactly what `slice::split_at` does internally. The difference: the SAFETY comment documents the invariant you proved. Numeric type punning โ€” use the safe API:
let bits: u32 = 1.5f32.to_bits();  // safe, explicit
let back: f32 = f32::from_bits(bits);
`to_bits`/`from_bits` are the safe equivalents of `transmute::<f32, u32>`. Prefer them always. Struct owning its data โ€” no transmute needed:
struct OwnedData { data: Vec<u8> }
impl OwnedData {
 fn as_slice(&self) -> &[u8] { &self.data }  // lifetime tied to &self automatically
}
The borrow checker infers `fn as_slice<'a>(&'a self) -> &'a [u8]` โ€” no unsafe required.

What This Unlocks

Key Differences

ConceptOCamlRust
Memory safetyGC prevents dangling refsBorrow checker + lifetime annotations
Type punning`Obj.magic` (escape hatch, no types)`transmute` (escape hatch, types must have same layout)
Safe split`Array.sub` (copies)`slice::split_at` (zero-copy, safe)
Lifetime extensionN/A (GC tracks roots)`transmute` or `from_raw_parts` + SAFETY doc
//! # 554. Safe Transmute with Lifetimes
//! When and how to safely transmute lifetime parameters.

/// UNSAFE: extend a lifetime (dangerous โ€” only safe if you guarantee validity)
/// This is the canonical example of what NOT to do:
unsafe fn extend_lifetime_unsafe<'short, 'long, T>(
    r: &'short T
) -> &'long T {
    std::mem::transmute(r)
}

/// SAFE PATTERN: lifetime extension with proven invariants
/// The `'static` item truly is static, but the type system needs help
fn get_static_str(s: &'static str) -> &'static str {
    s // no transmute needed โ€” already static
}

/// Safe slice from raw parts โ€” documented invariants
fn safe_split(data: &[i32], mid: usize) -> (&[i32], &[i32]) {
    assert!(mid <= data.len());
    // This is what split_at does internally via unsafe:
    unsafe {
        let ptr = data.as_ptr();
        let left  = std::slice::from_raw_parts(ptr, mid);
        let right = std::slice::from_raw_parts(ptr.add(mid), data.len() - mid);
        (left, right)
    }
}

/// Erase lifetime for a truly owned value (common pattern in custom allocators)
struct OwnedData {
    data: Vec<u8>,
}

impl OwnedData {
    fn new(data: Vec<u8>) -> Self { OwnedData { data } }

    /// SAFETY: returned slice is valid as long as OwnedData lives
    /// We use unsafe to express the lifetime relationship
    fn as_slice(&self) -> &[u8] {
        &self.data // safe โ€” normal borrow
    }

    /// Imaginary "reset with new data" that reuses the buffer
    fn reset(&mut self, new_data: &[u8]) {
        self.data.clear();
        self.data.extend_from_slice(new_data);
    }
}

/// Demonstrate why lifetime transmute is dangerous
fn danger_demo() {
    // DO NOT DO THIS:
    // let dangling: &'static str = unsafe {
    //     let local = String::from("I will be dropped");
    //     extend_lifetime_unsafe(&local)
    // }; // local dropped here โ€” dangling pointer!
    // println!("{}", dangling); // USE-AFTER-FREE!

    // DO THIS INSTEAD:
    let owned = String::from("safe owned data");
    let borrowed: &str = &owned;
    println!("Safe borrow: {}", borrowed);
    // borrowed only valid while owned lives โ€” borrow checker enforces this
}

/// Safe conversion patterns (alternatives to transmute)
fn safe_conversions() {
    // From/Into for type-safe conversions
    let s: String = String::from("hello");
    let bytes: Vec<u8> = s.into_bytes(); // safe, no transmute
    let s2 = String::from_utf8(bytes).unwrap(); // safe conversion back
    println!("round-trip: {}", s2);

    // as for numeric casts (safe for widening, truncating for narrowing)
    let x: i32 = 1000;
    let y: u8 = x as u8; // truncates โ€” documented behavior
    println!("i32 {} as u8 = {}", x, y);

    // bytemuck for safe type punning (crate, not std)
    // Without bytemuck: just use proper API
    let float: f32 = 1.5;
    let bits: u32 = float.to_bits(); // safe, explicit
    let back: f32 = f32::from_bits(bits);
    println!("f32 bits: {}, back: {}", bits, back);
}

fn main() {
    println!("=== Danger demo (conceptual) ===");
    danger_demo();

    println!("\n=== Safe split ===");
    let data = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = safe_split(&data, 3);
    println!("left: {:?}, right: {:?}", left, right);

    println!("\n=== OwnedData ===");
    let mut owned = OwnedData::new(b"hello world".to_vec());
    println!("slice: {:?}", std::str::from_utf8(owned.as_slice()).unwrap());
    owned.reset(b"new data");
    println!("after reset: {:?}", std::str::from_utf8(owned.as_slice()).unwrap());

    println!("\n=== Safe conversions ===");
    safe_conversions();
}

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

    #[test]
    fn test_safe_split() {
        let v = vec![1, 2, 3, 4, 5];
        let (l, r) = safe_split(&v, 2);
        assert_eq!(l, &[1, 2]);
        assert_eq!(r, &[3, 4, 5]);
    }

    #[test]
    fn test_owned_data() {
        let mut d = OwnedData::new(b"hello".to_vec());
        assert_eq!(d.as_slice(), b"hello");
        d.reset(b"world");
        assert_eq!(d.as_slice(), b"world");
    }

    #[test]
    fn test_f32_bits_roundtrip() {
        let f = std::f32::consts::PI;
        let bits = f.to_bits();
        assert_eq!(f32::from_bits(bits), f);
    }
}
(* Transmute concept in OCaml using Obj.magic -- very unsafe! *)
(* Showing the concept, not recommended practice *)

let () =
  (* Type coercion via Obj.magic (like transmute) *)
  let x : int = 42 in
  let y : float = Obj.magic x in  (* UNSAFE: reinterpret bits *)
  Printf.printf "transmuted: %.2f\n" y;  (* garbage value *)

  (* Safe alternative: proper conversion *)
  let safe = float_of_int x in
  Printf.printf "safe conversion: %.2f\n" safe;

  (* Extending lifetime is impossible in OCaml -- GC prevents dangling *)
  Printf.printf "OCaml: GC makes lifetime extension unnecessary\n"