Skip to content
FutureBlitz

    Install

    C interop, 2 of 7

    Strings and buffers

    Pass text to C as CStrings, keep a C string alive with CString.From, and share memory with C through a Buffer.

    What you’ll build

    A program that hands text and memory to C functions in three ways: a String passed straight to a CString parameter, a C string that you make, change, and free yourself, and a Buffer that C fills and FutureBlitz reads, sharing one block of memory without copying it.

    Strings for a call

    A String passed for a CString parameter is copied, with a NUL after it, right before the call, and freed right after it. That suits nearly every C function that reads text.

    Import std.core
    
    Extern "C"
        Function strlen(text: CString): UInt64
        Function strstr(text: CString, part: CString): String?
    End Extern
    
    Print(strlen("hello, world"))
    Print(strstr("hello, world", "wor") ?? "not found")
    Output
    12
    world

    Because the copy is freed after the call, C mustn’t keep the pointer. A function that gives back a pointer into its argument, as strstr does, is declared to give a String?, so its result is copied into a String before the argument’s copy goes. Declared to give a CString instead, it would only take CStrings you made yourself, which is the next step.

    A C string of your own

    CString.From(text) makes a C string that lasts until its Free(), for a C function that keeps the pointer or writes into it. Only Unsafe code reads its text, with ToString(), or frees it, since using one after it’s freed would break memory.

    Import std.core
    Import std.ffi
    
    Extern "C"
        Function toupper(c: Int32): Int32
    End Extern
    
    Local name = CString.From("ada")
    Unsafe
        Local letters = name As Ptr<UInt8>
        letters[0] = toupper(letters[0] As Int32) As UInt8
        Print(name.ToString())
        name.Free()
    End Unsafe
    Output
    Ada

    name As Ptr<UInt8> sees the C string as its bytes, which the code changes in place. A CString’s zero is Null, C’s NULL, and ToString() on a Null one panics.

    A Buffer that C fills

    std.ffi’s Buffer<T> is memory for a number of Ts, all 0 at first, at an address that doesn’t move. C reads and writes it through its Pointer(), and FutureBlitz code reads and sets it with [], which checks each index in every build.

    Import std.core
    Import std.ffi
    
    Extern "C"
        Function memset(target: Ptr<Void>, value: Int32, count: UInt64): Ptr<Void>
    End Extern
    
    Local bytes = Buffer<UInt8>(8)
    Unsafe
        memset(bytes.Pointer(), 42, 5)
    End Unsafe
    Print(bytes.ToArray())
    
    bytes[7] = 1
    Print(bytes[7], bytes.Count)
    Print(bytes.Slice(2, 6).ToArray())
    Output
    [42, 42, 42, 42, 42, 0, 0, 0]
    1 8
    [42, 42, 42, 0]

    Only Unsafe code gets the Pointer(), which is good while the Buffer is. Slice(start, finish) is a view of part of it: unlike an array’s slice, which copies, a Buffer’s Slice shares its memory, so a change through either one shows in the other, and the view keeps the Buffer alive. ToArray() copies its contents into an array. A Buffer frees its memory once nothing refers to it, as any object does.

    What you learned

    • A String passed for a CString parameter is copied for the call and freed after it.
    • CString.From makes a C string that lasts until Free(), which Unsafe code calls.
    • Buffer<T> is memory that C and FutureBlitz share, checked by [] and freed automatically.
    • Pointer() hands a Buffer to C, from Unsafe code.
    • A Buffer’s Slice is a view that shares its memory, where an array’s slice is a copy.

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