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 Unsafe16
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)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 Unsafe30 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)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 UnsafeTrue
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
AddressOfonly in Unsafe code. Mem.AllocandMem.Freemanage C memory, andDeferfrees it when the block ends.- An
Unsafe Functionis called only from Unsafe code. p.Valueandp[i]read and write what a Ptr points at, andSizeOf<T>()gives a type’s size.- A debug build panics on a Null Ptr’s use.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.