Raw Pointer Basics
Functional Programming
Tutorial
The Problem
Raw pointers (*const T and *mut T) are Rust's lowest-level memory access primitive — the equivalent of C's pointers. They bypass the borrow checker entirely: no lifetime tracking, no aliasing rules, no validity guarantees. They are essential for FFI, custom allocators, lock-free data structures, and performance-critical code that cannot afford the overhead of safe abstractions. The safe-wrapper idiom encapsulates raw pointer operations behind a safe function boundary, isolating the unsafe footprint.
🎯 Learning Outcomes
*const T and *mut T differ from references: no lifetime, no borrow rulesslice.as_ptr(), &x as *const Tptr.add(idx) performs bounds-checked offset arithmeticMaybeUninit initializationCode Example
#![allow(clippy::all)]
//! 699 — Raw Pointer Basics
//! *const T and *mut T: creation, casting, and safe wrapping.
/// Read `idx` from a slice via raw pointer, returning None for out-of-bounds.
fn safe_read(slice: &[u32], idx: usize) -> Option<u32> {
if idx >= slice.len() {
return None;
}
let ptr: *const u32 = slice.as_ptr();
Some(unsafe {
// SAFETY: idx < slice.len() checked above; ptr is valid for
// slice.len() elements; alignment guaranteed by slice invariant.
*ptr.add(idx)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_read_in_bounds() {
let arr = [10u32, 20, 30];
assert_eq!(safe_read(&arr, 0), Some(10));
assert_eq!(safe_read(&arr, 2), Some(30));
}
#[test]
fn test_safe_read_out_of_bounds() {
let arr = [10u32, 20, 30];
assert_eq!(safe_read(&arr, 3), None);
assert_eq!(safe_read(&arr, usize::MAX), None);
}
#[test]
fn test_raw_ptr_mut_write() {
let mut val: i64 = 7;
let p: *mut i64 = &mut val;
unsafe {
// SAFETY: p derived from `val` still live; no other references.
*p = 42;
}
assert_eq!(val, 42);
}
#[test]
fn test_const_ptr_read() {
let data = [100u8, 200, 150];
let ptr: *const u8 = data.as_ptr();
let second = unsafe {
// SAFETY: offset 1 < data.len() = 3; valid, aligned.
*ptr.add(1)
};
assert_eq!(second, 200);
}
}Key Differences
unsafe; OCaml's GC manages all pointers transparently.// SAFETY: comments explaining why each unsafe block is sound; OCaml has no equivalent because unsafe is not part of the language.ctypes library wraps C pointers in type-safe OCaml values.OCaml Approach
OCaml's GC manages all memory — raw pointer access is not part of normal OCaml programming. Unsafe memory operations use Bigarray, Bytes, or the ctypes FFI library:
(* OCaml equivalent: use array indexing with bounds checking *)
let safe_read arr idx =
if idx >= Array.length arr then None
else Some arr.(idx)
Full Source
#![allow(clippy::all)]
//! 699 — Raw Pointer Basics
//! *const T and *mut T: creation, casting, and safe wrapping.
/// Read `idx` from a slice via raw pointer, returning None for out-of-bounds.
fn safe_read(slice: &[u32], idx: usize) -> Option<u32> {
if idx >= slice.len() {
return None;
}
let ptr: *const u32 = slice.as_ptr();
Some(unsafe {
// SAFETY: idx < slice.len() checked above; ptr is valid for
// slice.len() elements; alignment guaranteed by slice invariant.
*ptr.add(idx)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_read_in_bounds() {
let arr = [10u32, 20, 30];
assert_eq!(safe_read(&arr, 0), Some(10));
assert_eq!(safe_read(&arr, 2), Some(30));
}
#[test]
fn test_safe_read_out_of_bounds() {
let arr = [10u32, 20, 30];
assert_eq!(safe_read(&arr, 3), None);
assert_eq!(safe_read(&arr, usize::MAX), None);
}
#[test]
fn test_raw_ptr_mut_write() {
let mut val: i64 = 7;
let p: *mut i64 = &mut val;
unsafe {
// SAFETY: p derived from `val` still live; no other references.
*p = 42;
}
assert_eq!(val, 42);
}
#[test]
fn test_const_ptr_read() {
let data = [100u8, 200, 150];
let ptr: *const u8 = data.as_ptr();
let second = unsafe {
// SAFETY: offset 1 < data.len() = 3; valid, aligned.
*ptr.add(1)
};
assert_eq!(second, 200);
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_read_in_bounds() {
let arr = [10u32, 20, 30];
assert_eq!(safe_read(&arr, 0), Some(10));
assert_eq!(safe_read(&arr, 2), Some(30));
}
#[test]
fn test_safe_read_out_of_bounds() {
let arr = [10u32, 20, 30];
assert_eq!(safe_read(&arr, 3), None);
assert_eq!(safe_read(&arr, usize::MAX), None);
}
#[test]
fn test_raw_ptr_mut_write() {
let mut val: i64 = 7;
let p: *mut i64 = &mut val;
unsafe {
// SAFETY: p derived from `val` still live; no other references.
*p = 42;
}
assert_eq!(val, 42);
}
#[test]
fn test_const_ptr_read() {
let data = [100u8, 200, 150];
let ptr: *const u8 = data.as_ptr();
let second = unsafe {
// SAFETY: offset 1 < data.len() = 3; valid, aligned.
*ptr.add(1)
};
assert_eq!(second, 200);
}
}
Exercises
fn every_nth(slice: &[i32], n: usize) -> Vec<i32> using raw pointer arithmetic — validate n > 0 and bounds check before each unsafe dereference.unsafe fn swap_raw<T>(a: *mut T, b: *mut T) and a safe wrapper fn safe_swap<T>(a: &mut T, b: &mut T) — include SAFETY documentation.