Course contentsShow
← Rust
Lesson 1415 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Pointer Arithmetic and Offset Calculation

You will navigate memory structures using pointer arithmetic while strictly adhering to bounds constraints.

In this lesson, you will learn how to navigate memory manually by calculating addresses using pointer arithmetic while ensuring you stay strictly within allocated bounds.

The Mechanics of Pointer Arithmetic

In Rust, raw pointers (*const T or *mut T) do not support arithmetic operators like + or - directly, because the compiler doesn't know the size of the type behind the pointer. When we perform pointer arithmetic, we aren't just adding raw bytes to an address; we are adding or subtracting multiples of the size of the underlying type.

To perform this safely, we use the add and sub methods available on raw pointers. These methods automatically scale the offset by size_of::<T>().

Calculating Offsets Safely

Consider a scenario where you have a pointer to an array of integers. To access the third element, you don't add 8 bytes (on a 64-bit system); you add an offset of 2 to the base pointer.

let data = [10, 20, 30, 40];
let ptr = data.as_ptr();

unsafe {
    // We add an offset of 2 to move to the third element
    let offset_ptr = ptr.add(2);
    println!("Value at offset 2: {}", *offset_ptr); // Prints 30
}

The Bound Constraints

The most critical rule in pointer arithmetic is that the resulting pointer must remain within the bounds of the original allocated object.

  1. In-bounds: The pointer can point to any element within the allocated block.
  2. One-past-the-end: You are permitted to point to the address immediately following the last element. This is often used as a loop sentinel.
  3. Out-of-bounds: Any pointer calculation that moves beyond the object's memory—or points to unallocated memory—triggers Undefined Behavior.

Unlike C, where pointer arithmetic is often loose, Rust’s add and sub methods expect you to respect the provenance of the allocation. If you calculate an address that exceeds the original buffer, you have violated the safety contract, even if you never dereference that specific pointer. Always ensure your index calculations are verified against the known length of your allocation before executing the arithmetic.

Key Takeaway: Pointer arithmetic in Rust scales by the size of the type, and you must strictly ensure your resulting pointer remains within the bounds of the original allocation or at most one byte past the end.