Mojo Pointers

A pointer stores the memory address of another value rather than the value itself. Pointers let you work with memory directly — allocating, reading, writing, and freeing blocks of data. Mojo provides UnsafePointer for low-level control and is developing safe pointer types for everyday use.

The Address Analogy

  Memory is like a street of numbered houses.
  Each house holds one value.

  House 1000: [ 42 ]
  House 1001: [ 99 ]
  House 1002: [  7 ]

  A pointer is a piece of paper that says "go to house 1000."
  The pointer itself lives at some other address.

  ptr → 1000 → [ 42 ]

  Following the pointer to read the value = "dereferencing."

UnsafePointer Basics

UnsafePointer is Mojo's raw pointer type. It gives you direct memory access with no safety guarantees — you are responsible for correct use. Use it only when performance demands it or when interfacing with C libraries.

from memory import UnsafePointer

fn main():
    # Allocate memory for one Int
    var ptr = UnsafePointer[Int].alloc(1)

    # Write a value to the allocated memory
    ptr.init_pointee_copy(42)

    # Read the value back
    print(ptr[0])   # 42

    # Free the memory when done
    ptr.destroy_pointee()
    ptr.free()

Pointer Lifecycle

  1. alloc(n)          → reserve n slots of memory
  2. init_pointee_*()  → write initial value(s) into the memory
  3. ptr[i]            → read value at index i
  4. ptr[i] = v        → write value at index i
  5. destroy_pointee() → run destructor on stored value
  6. free()            → release the memory back to the system

  Skip step 5 or 6 → memory leak
  Use memory after step 6 → undefined behavior (crash or corrupt data)

Allocating Multiple Elements

from memory import UnsafePointer

fn main():
    let n = 5
    var arr = UnsafePointer[Float64].alloc(n)

    # Initialize all elements
    for i in range(n):
        arr.init_pointee_copy(Float64(i) * 1.5)

    # Read all elements
    for i in range(n):
        print(arr[i], end=" ")   # 0.0 1.5 3.0 4.5 6.0
    print("")

    # Clean up
    for i in range(n):
        (arr + i).destroy_pointee()
    arr.free()
Memory layout for 5 Float64 values:
  arr → [ 0.0 | 1.5 | 3.0 | 4.5 | 6.0 ]
          ↑     ↑     ↑     ↑     ↑
        arr+0 arr+1 arr+2 arr+3 arr+4

Pointer Arithmetic

Adding an integer to a pointer advances it by that many elements (not bytes). This is how you navigate through a block of memory.

from memory import UnsafePointer

fn main():
    var data = UnsafePointer[Int].alloc(3)
    data.init_pointee_copy(10)
    (data + 1).init_pointee_copy(20)
    (data + 2).init_pointee_copy(30)

    print(data[0])         # 10
    print((data + 1)[0])   # 20
    print((data + 2)[0])   # 30

    for i in range(3):
        (data + i).destroy_pointee()
    data.free()
Pointer arithmetic:
  data   → address 1000 → value 10
  data+1 → address 1008 → value 20   (8 bytes for Int64)
  data+2 → address 1016 → value 30

Null Pointer Check

An uninitialized or failed allocation produces a null pointer. Dereferencing null causes a crash. Always check before using a pointer from an external source.

from memory import UnsafePointer

fn main():
    var ptr = UnsafePointer[Int]()   # null pointer

    if ptr:
        print("Valid pointer, safe to use")
    else:
        print("Null pointer — do not dereference")

Passing Pointers to Functions

from memory import UnsafePointer

fn fill(ptr: UnsafePointer[Int], count: Int, value: Int):
    for i in range(count):
        ptr[i] = value

fn sum_array(ptr: UnsafePointer[Int], count: Int) -> Int:
    var total = 0
    for i in range(count):
        total += ptr[i]
    return total

fn main():
    var buf = UnsafePointer[Int].alloc(4)
    for i in range(4):
        buf.init_pointee_copy(0)

    fill(buf, 4, 7)
    print(sum_array(buf, 4))   # 28

    for i in range(4):
        (buf + i).destroy_pointee()
    buf.free()

When to Use Pointers

Situation                              | Use Pointer?
---------------------------------------|-------------
Interfacing with a C library            | Yes
Writing a custom memory allocator       | Yes
Building a high-performance data struct | Yes
General application logic               | No — use List, String, structs
Anything where safety matters most      | No — use safe types

Key Takeaways

A pointer holds a memory address. UnsafePointer gives raw memory control with no automatic safety. Always follow the full lifecycle: allocate, initialize, use, destroy, free. Pointer arithmetic moves in units of the element size. Check for null before dereferencing external pointers. Reserve pointers for system-level work and performance-critical data structures — safe Mojo types cover the vast majority of everyday programming needs.

Leave a Comment

Your email address will not be published. Required fields are marked *