Skip to content
FutureBlitz

    Install

    C interop, 3 of 7

    Pointers and Unsafe

    Allocate C memory, read and write through Ptrs, and keep that code inside Unsafe blocks.

    What you’ll build

    A program that allocates memory for five Int32s the way C does, fills it with squares, sums it in an Unsafe Function, steps a pointer through it, and changes a Struct through its address. It ends by reading through a Null pointer, to show what a debug build does then.

    Unsafe code

    Ordinary FutureBlitz code can’t break memory: every index is checked and every object is freed for you. Pointers can, so the code that uses them is marked. An Unsafe block is where code can dereference a Ptr, step it, take an address with AddressOf, and call Mem’s functions.

    Import std.core
    Import std.ffi
    
    Unsafe
        Local squares: Ptr<Int32> = Mem.Alloc(5 * SizeOf<Int32>())
        Defer Mem.Free(squares)
        For i = 0 Until 5
            squares[i] = i * i
        End For
        Print(squares[4])
    End Unsafe
    Output
    16

    Mem.Alloc takes a size in bytes and gives zeroed memory, a Ptr<Void>, which converts to any Ptr<T>. SizeOf<Int32>() is 4. squares[i] reads and writes the ith Int32, and Defer frees the memory when the block ends, however it ends.

    Outside Unsafe code, the same call is an error:

    Import std.core
    Import std.ffi
    
    Local squares: Ptr<Int32> = Mem.Alloc(5 * SizeOf<Int32>())
    Print(squares == Null)
    $ fb run main.fb
       Compiling main.fb
    main.fb:4:33: error: calling 'Alloc', which is Unsafe, needs Unsafe code: an Unsafe block, Function, or Method
      |
    4 | Local squares: Ptr<Int32> = Mem.Alloc(5 * SizeOf<Int32>())
      |                                 ^^^^^
    error: couldn't build main.fb because of 1 error

    Unsafe Functions

    An Unsafe Function is all Unsafe code, and only Unsafe code can call it. It’s for a function whose arguments, if they’re wrong, break memory: here, a pointer and a count that must agree.

    Import std.core
    Import std.ffi
    
    Unsafe Function Sum(values: Ptr<Int32>, count: Int): Int
        Local total = 0
        For i = 0 Until count
            total += values[i]
        End For
        Return total
    End Function
    
    Unsafe
        Local squares: Ptr<Int32> = Mem.Alloc(5 * SizeOf<Int32>())
        Defer Mem.Free(squares)
        For i = 0 Until 5
            squares[i] = i * i
        End For
        Print(Sum(squares, 5))
    
        Local third = squares + 2
        Print(third.Value, third - squares)
    End Unsafe
    Output
    30
    4 2

    squares + 2 steps two Int32s along, 8 bytes, and third.Value is what it points at. Subtracting two Ptrs counts the elements between them.

    Addresses of your own values

    AddressOf(x) gives a Ptr to a Local, a field, or an array’s element. Through it, p.Value reads and changes the value itself. A Ptr points only at what C can have, so the Struct is @CLayout, laid out as C would.

    Import std.core
    Import std.ffi
    
    @CLayout Struct Point
        Field X: Int32
        Field Y: Int32
    End Struct
    
    Local here = Point(1, 2)
    Unsafe
        Local p = AddressOf(here)
        p.Value.X = 10
    End Unsafe
    Print(here.X, here.Y)
    Output
    10 2

    The pointer is good only while here is: once its block ends, the pointer points at nothing.

    A Null pointer

    A Ptr’s zero is Null, which safe code can compare against. A debug build panics where Unsafe code reads through a Null Ptr, so the mistake shows where it is.

    Import std.core
    Import std.ffi
    
    Local nothing: Ptr<Int32> = Null
    Print(nothing == Null)
    Unsafe
        Print(nothing.Value)
    End Unsafe
    Output
    True
    panic: the Ptr is Null at main.fb:7:11
        at top-level code (main.fb:7)

    Unsafe code can still break memory in ways no check finds, like reading past the end of what Mem.Alloc gave. Keep it small, and wrap it in safe functions that check their arguments.

    What you learned

    • Pointers are dereferenced, stepped, and taken with AddressOf only in Unsafe code.
    • Mem.Alloc and Mem.Free manage C memory, and Defer frees it when the block ends.
    • An Unsafe Function is called only from Unsafe code.
    • p.Value and p[i] read and write what a Ptr points at, and SizeOf<T>() gives a type’s size.
    • A debug build panics on a Null Ptr’s use.

    Report an issue with this page or with FutureBlitz on GitHub.