Skip to main content

Mojo module

unsafe_pointer

Aliases

ExternalImmutPointer

alias ExternalImmutPointer[type: AnyType, *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[type, origin_of(MutOrigin.external), address_space=address_space]

An immutable ExternalPointer.

Parameters

ExternalMutPointer

alias ExternalMutPointer[type: AnyType, *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[type, origin_of(MutOrigin.external), address_space=address_space]

A mutable ExternalPointer.

Parameters

ExternalPointer

alias ExternalPointer[type: AnyType, *, mut: Bool = False, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[type, origin_of(Origin[mut].external), address_space=address_space]

ExternalPointer[T] is an alias of UnsafePointerV2 with an external origin, informing the compiler this pointer does not alias any existing values.

Unlike UnsafePointer, ExternalPointer does not have a parametric origin making it unable to alias any existing values. This can lead to unexpected behavior if not used correctly. Before you use ExternalPointer, consider using a more memory safe type instead: Pointer[T], Span[T], ref T, or OwnedPointer[T]. If you do need the unsafe functionality of pointer-like behavior (pointer arithmetic, unchecked dereferencing etc.) and are unable to use a memory-safe abstraction, consider using UnsafePointer with a parameterized origin.

Safety:

  • Origin management: An ExternalPointer does not alias any values. See below for more information on how to correctly use origins with ExternalPointer.
  • All safety requirements of UnsafePointer apply to ExternalPointer as well, including: manual memory management, null checking, uninitialized memory access, and bounds checking.

Notes:

Correct origin usage:

ExternalPointer[T] contains an external origin, meaning it points to memory that does not alias any other values. Using an ExternalPointer will not extend the lifetime of any other values in scope. This includes returning an ExternalPointer from a struct's member function, as it will not extend the struct's lifetime even though the struct holds a copy of the same pointer. It is seldom correct to return an ExternalPointer from a member function.

Consider the following example:

struct MyContainer[T: AnyType & Movable]:
    var data: ExternalMutPointer[T]

    fn __init__(out self, var value: T)
        self.data = alloc[T](1)
        self.data.init_pointee_move(value^)

    fn __del__(deinit self):
        self.data.destroy_pointee()
        self.data.free()

    fn get_underlying_pointer(self) -> ExternalMutPointer[T]:
        return self.data

def main():
    var container = MyContainer[Int](42)
    var ptr = container.get_underlying_pointer()
    # container^.__del__() will be called here, deallocating the pointer.

    # `ptr` now points to deallocated memory.
    print(ptr[]) # <-- Undefined behavior!

The above example shows how easily undefined behavior can arise when using ExternalPointer incorrectly. Due to Mojo's ASAP destruction rules, the compiler will destroy container before ptr is read from - meaning ptr now points to deallocated memory. This is because ptr does not have an origin tied back to container.

To prevent bugs like this, get_underlying_pointer should be implemented in one of the following ways.

Option 1:

Have this function "consume" self via deinit. This will - at compile time - prevent the usage of container after this function is called and it will prevent container from deallocating the memory as its __del__ method will not be called by the compiler.

struct MyContainer[T: AnyType & Movable]:
    var data: ExternalMutPointer[T]

    # Notice the use of `deinit` to consume `self`.
    fn get_underlying_pointer(deinit self) -> ExternalMutPointer[T]:
        return self.data

def main():
    var container = MyContainer[Int](42)
    # Notice the `^` transfer sigil.
    var ptr = container^.get_underlying_pointer()

    # This will print `42` since `container.__del__()` is not called, leaving
    # the allocated memory intact.
    print(ptr[])

    # Don't forget the cleanup the memory!
    ptr.destroy_pointee()
    ptr.free()

Option 2:

Have the function return a pointer type with an origin tied back to self. This will inform the compiler that the returned pointer is aliasing container, preventing the compiler from destroying container until ptr and container are both unused.

struct MyContainer[T: AnyType & Movable]:
    var data: ExternalMutPointer[T]

    # Notice the use of `origin=origin_of(self)`.
    fn get_underlying_pointer(
        self,
    ) -> UnsafeImmutPointer[T, origin=origin_of(self)]:
        return self.data
            # cast the pointer to immutable
            .as_immutable()
            # cast the origin to the origin of `self`
            .unsafe_origin_cast[origin_of(self)]()

def main():
    var container = MyContainer[Int](42)
    var ptr = container.get_underlying_pointer()
    # The compiler does *not* destroy `container` here, as `ptr` contains
    # an origin tied back to `container`.

    print(ptr[]) # <-- This will now print `42`!

    # `container^.__del__()` will be called here, freeing the memory.

Option 3:

Ensure the function resets its data field to null. This will ensure that when self is destroyed, it does not deallocate the memory exposed via steal_underlying_pointer. This option should seldom be used as it can still lead to bugs if self does not account for a null pointer.

struct MyContainer[T: AnyType & Movable]:
    var data: ExternalMutPointer[T]

    fn steal_underlying_pointer(
        mut self,
    ) -> ExternalMutPointer[T]:
        var result = self.data
        self.data = {} # <-- Reset the data field to null
        return result

    fn __del__(deinit self):
        # Since `data` can now be null, we need to add explicit null checks.
        if self.data:
            self.data.destroy_pointee()
            self.data.free()

def main():
    var container = MyContainer[Int](42)
    var ptr = container.steal_underlying_pointer()

    # `container.__del__()` *is* called here, but is now null, leaving
    #`ptr` pointing to valid memory.

    print(ptr[]) # <-- This will now print `42`!

    # Don't forget the cleanup the memory!
    ptr.destroy_pointee()
    ptr.free()

When to Use ExternalPointer:

  1. Memory Allocation

ExternalPointer is the return type of alloc() because newly allocated memory has no origin aliasing pre-existing values. Data structures containing heap allocated memory often hold an ExternalPointer.

  1. Foreign Function Interface (FFI)

When interfacing with C functions via external_call, ExternalPointer is often the correct type to use as a return type or as an out parameter. This is because C functions can return pointers to memory outside the known origins in the Mojo program.

from sys.ffi import external_call

# `getenv` returns a pointer to memory outside of the Mojo program.
#
# Notably a return type of `ExternalPointer[Byte]` is immutable by default as
# we are not allowed to modify the memory it points to.
#
# If the FFI function returned a mutable pointer, we could specify `mut=True`.
var env_ptr = external_call["getenv", ExternalPointer[Byte]](
    # parameters...
)
  1. Memory Management Handoff

Use ExternalPointer when a function needs to "leak" or "give up" its pointer with the expectation that the caller is now responsible for proper management and cleanup. This however, should generally be avoided as it can lead to undefined behavior if not implemented correctly. See the section on "Correct origin usage" above for more information.

When NOT to Use ExternalPointer:

  1. Function Arguments

It is almost never correct to take an ExternalPointer as an argument to a function. Prefer using a more memory safe type instead: e.g. Pointer[T] or Span[T] when the function does not need to take ownership of the pointer. Use OwnedPointer[T] when the function does need to take ownership of the pointer.

  1. Return types of public member functions.

Data structures should almost never expose their internal ExternalPointer via public functions unless that function is specifically designed to transfer ownership and management responsibility to the caller. Instead, public methods should return references or memory safe reference types: e.g. Pointer[T] or Span[T].

Parameters

  • type (AnyType): The type the pointer points to.
  • mut (Bool): Whether the pointer is mutable - defaults to False.
  • address_space (AddressSpace): The address space associated with the pointer.

OpaqueImmutPointer

alias OpaqueImmutPointer[origin: ImmutOrigin, *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[NoneType, origin, address_space=address_space]

An immutable opaque pointer, equivalent to the C const void* type.

Parameters

OpaqueMutPointer

alias OpaqueMutPointer[origin: MutOrigin, *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[NoneType, origin, address_space=address_space]

A mutable opaque pointer, equivalent to the C void* type.

Parameters

OpaquePointerV2

alias OpaquePointerV2[mut: Bool, //, origin: Origin[mut], *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[NoneType, origin, address_space=address_space]

An opaque pointer, equivalent to the C (const) void* type.

Parameters

UnsafeImmutPointer

alias UnsafeImmutPointer[type: AnyType, origin: ImmutOrigin, *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[type, origin, address_space=address_space]

An immutable unsafe pointer.

Parameters

UnsafeMutPointer

alias UnsafeMutPointer[type: AnyType, origin: MutOrigin, *, address_space: AddressSpace = AddressSpace.GENERIC] = UnsafePointerV2[type, origin, address_space=address_space]

A mutable unsafe pointer.

Parameters

Structs

  • UnsafePointerV2: UnsafePointerV2[T] represents an indirect reference to one or more values of type T consecutively in memory, and can refer to uninitialized memory.

Functions

  • alloc: Allocates contiguous storage for count elements of type with alignment alignment.

Was this page helpful?