5.1.36. Pointers

This tutorial covers pointer types, creation, dereferencing, null safety, pointer arithmetic, and low-level operations like reinterpret and intptr.

5.1.36.1. Pointer types

In daslang, T? is a nullable pointer to T:

var p : int?           // pointer to int — null by default
var ps : Point?        // pointer to struct
var vp : void?         // untyped (void) pointer

Pointers are used for heap-allocated data, optional references, and low-level interop. Unlike C/C++, field access auto-dereferences:

p.x      // same as (*p).x — no -> operator needed

5.1.36.2. Creating pointers with new

new allocates on the heap and returns a pointer:

var p = new Point(x = 3.0, y = 4.0)  // p is Point?

Fields get default values when omitted:

var q = new Point()     // x = 0.0, y = 0.0

Heap pointers must be freed explicitly with delete (requires unsafe), or use var inscope for automatic cleanup:

var inscope pt = new Point(x = 1.0, y = 2.0)
// pt is automatically deleted when it goes out of scope

5.1.36.3. addr and safe_addr

addr(x) returns a pointer to an existing variable. It requires unsafe because the pointer could outlive the variable:

var x = 42
unsafe {
    var p = addr(x)    // p is int?
    *p = 100           // modifies x through the pointer
}

safe_addr from daslib/safe_addr returns a temporary pointer (T?#) without requiring unsafe:

require daslib/safe_addr
var a = 13
var p = safe_addr(a)   // p is int?# — safe, no unsafe needed
print("{*p}\n")

5.1.36.4. Dereferencing

*p and deref(p) follow the pointer to the value. Both panic if the pointer is null:

unsafe {
    var y = 7
    var p = addr(y)
    print("{*p}\n")         // 7
    print("{deref(p)}\n")   // 7
}

For struct pointers, . auto-dereferences — no -> operator:

var inscope pt = new Point(x = 5.0, y = 6.0)
print("{pt.x}\n")           // 5 — same as (*pt).x

5.1.36.5. Null safety

Uninitialized pointers are null. Dereferencing null panics:

var np : int?       // null
try {
    print("{*np}\n")
} recover {
    print("null deref caught\n")
}

Use null checks, safe navigation, or null coalescing to handle nullable pointers safely:

options gen2

struct Point {
    x : float
    y : float
}

def get_x(p : Point?) : float {
    return p?.x ?? -1.0    // -1.0 if p is null
}

?. returns null if the pointer is null (no panic). ?? provides a fallback value when the left side is null.

5.1.36.6. Passing pointers to functions

Pointers can be passed as function arguments. Functions can read from and write through them:

def double_value(p : int?) {
    *p = *p + *p
}

var val = 21
unsafe {
    double_value(addr(val))
}
print("{val}\n")  // 42

Struct pointer arguments auto-deref for field access:

def move_point(p : Point?; dx : float; dy : float) {
    p.x += dx    // auto-deref
    p.y += dy
}

5.1.36.7. Deletion

delete frees heap memory and sets the pointer to null. Requires unsafe:

var p = new Point(x = 1.0, y = 2.0)
unsafe {
    delete p     // memory freed, p is now null
}

Prefer var inscope over manual delete — it adds a finally block that automatically cleans up the pointer when the scope exits.

5.1.36.8. Pointer arithmetic

Pointer indexing and arithmetic are unsafe. They operate on raw memory with no bounds checking:

var arr <- [10, 20, 30, 40, 50]
unsafe {
    var p = addr(arr[0])
    print("{p[0]}, {p[2]}\n")     // 10, 30

    ++ p            // advance by one element
    print("{*p}\n") // 20

    p += 2           // advance by two more
    print("{*p}\n") // 40
}

Warning

Pointer arithmetic can easily cause out-of-bounds access. No runtime bounds checking is performed.

5.1.36.9. void pointers

void? is an untyped pointer — equivalent to void* in C. Used for C/C++ interop where the actual type is opaque. Must reinterpret back to a typed pointer before use:

unsafe {
    var x = 123
    var px = addr(x)
    var vp : void? = reinterpret<void?> px   // erase type
    var px2 = reinterpret<int?> vp           // restore type
    print("{*px2}\n")                        // 123
}

5.1.36.10. intptr

intptr(p) converts a pointer to a uint64 integer representing its memory address. Useful for debugging, logging, or identity comparisons:

unsafe {
    var x = 42
    var p = addr(x)
    print("address: {intptr(p)}\n")
}

5.1.36.11. reinterpret

reinterpret<T> performs a raw bit cast between types of the same size. Requires unsafe:

unsafe {
    let f = 1.0
    let bits = reinterpret<int> f    // IEEE 754: 0x3f800000
    let back = reinterpret<float> bits
    print("{back}\n")                // 1
}

Warning

reinterpret does not convert values — it reinterprets raw bits. The source and target types must have the same size.

See also

Pointers — pointer language reference.

Unsafe — unsafe operations reference.

Values and Data Types — all data types including smart pointers.

Full source: tutorials/language/36_pointers.das

Previous tutorial: Job Queue (jobque)

Next tutorial: Utility Patterns (defer + static_let)