How to Create a Persistent String Using mulle-buffer

To create a persistent string using mulle-buffer, first ensure the buffer is null-terminated with mulle_buffer_make_string(), then call mulle_buffer_extract_string() to obtain a heap-allocated C string that remains valid after the buffer itself is destroyed.

The mulle-c/mulle-buffer repository provides a dynamic byte array implementation for C that can be safely converted into independent, null-terminated strings. Unlike temporary buffer contents that become invalid when the buffer scope ends, properly extracted strings persist on the heap until explicitly freed. This guide demonstrates how to create a persistent string using mulle-buffer through the exact API calls and macro-based workflows defined in the source code.

The Two-Step Process for Persistent Strings

Creating a persistent string requires two distinct operations: ensuring null termination and detaching the data from the buffer structure. According to the implementation in src/mulle--buffer.c, these steps guarantee that the returned pointer remains valid independent of the original buffer's lifetime.

Step 1: Null-Terminate with mulle_buffer_make_string

Before extraction, the buffer must contain a terminating '\0' byte. The function mulle_buffer_make_string() (backed by _mulle__buffer_make_string in src/mulle--buffer.c at lines 196-215) appends a null terminator if one is not already present. This function returns status codes indicating whether the buffer required modification, ensuring the internal storage is properly prepared for C string operations.

Step 2: Extract with mulle_buffer_extract_string

The mulle_buffer_extract_string() function, declared as an inline wrapper in src/mulle-buffer.h at lines 702-710, performs the actual extraction:

static inline void *mulle_buffer_extract_string( struct mulle_buffer *buffer )
{
    return( _mulle__buffer_extract_string( (struct mulle__buffer *) buffer,
                                           buffer->_allocator));
}

Internally, the implementation in src/mulle--buffer.c (lines 218-227) first invokes the make_string logic, then shrinks the allocation to the exact size via _mulle__buffer_size_to_fit, and finally returns the pointer. The returned string is allocated using the buffer's configured allocator and must be freed with mulle_free() when no longer needed.

Manual API Implementation

For explicit control over buffer lifecycle and string extraction, use the manual API. This approach is defined in src/mulle-buffer.h and allows you to manage each allocation step discretely.

#include "mulle-buffer.h"

int main(void)
{
    struct mulle_buffer *buf;
    char               *persistent;

    /* 1️⃣  Create a flexible buffer that will grow as needed */
    buf = mulle_buffer_create( NULL );                     /* uses default allocator */

    /* 2️⃣  Build the string */
    mulle_buffer_add_string( buf, "Hello, " );
    mulle_buffer_add_string( buf, "world!" );

    /* 3️⃣  Ensure null‑termination (optional – extract does it anyway) */
    mulle_buffer_make_string( buf );

    /* 4️⃣  Extract a persistent copy */
    persistent = mulle_buffer_extract_string( buf );

    /* 5️⃣  The original buffer can now be destroyed safely */
    mulle_buffer_destroy( buf );

    /* 6️⃣  Use the string */
    printf("%s\n", persistent);

    /* 7️⃣  Free the persistent copy when done */
    mulle_free( persistent );
    return 0;
}

Key API references from the source:

Automated Extraction with mulle_buffer_do_string

For most use cases, the mulle_buffer_do_string macro automates buffer creation, string extraction, and cleanup. Defined in src/mulle-buffer.h at lines 640-679, this macro uses a complex for-loop construct to manage scope automatically.

The macro expansion creates a temporary mulle_buffer on the stack (or via the provided allocator), executes your code block, automatically calls mulle_buffer_extract_string, and cleans the temporary buffer when exiting the scope.

#include "mulle-buffer.h"

int main(void)
{
    char *s;

    /* The macro handles creation, extraction and cleanup */
    mulle_buffer_do_string( buf, NULL, s )
    {
        mulle_buffer_add_string( buf, "Persistent " );
        mulle_buffer_add_string( buf, "string created with a macro" );
        break;               /* break out of the block – macro will still extract */
    }

    /* `s` now points to a heap‑allocated, null‑terminated string */
    printf("%s\n", s);
    mulle_free( s );         /* remember to free it */
    return 0;
}

The macro definition itself (lines 640-679) implements a state machine using pointer arithmetic to ensure extraction happens exactly once after the block executes, even if you exit early with a break statement.

Custom Allocator Support

When working with non-standard memory management, use mulle_buffer_do_allocator (defined in src/mulle-buffer.h at lines 731-764) to inject a custom allocator for both the temporary buffer and the final extracted string.

#include "mulle-buffer.h"
#include "mulle-allocator.h"

int main(void)
{
    struct mulle_allocator *alloc = mulle_allocator_create();
    char *s;

    mulle_buffer_do_allocator( buf, alloc, s )
    {
        mulle_buffer_add_string( buf, "Allocated with custom allocator" );
    }

    printf("%s\n", s);
    mulle_free( s );
    mulle_allocator_destroy( alloc );
    return 0;
}

This pattern ensures that mulle_buffer_extract_string uses your specified allocator when shrinking the allocation to fit and returning the persistent pointer.

Summary

  • Persistent strings require explicit extraction from a mulle_buffer using mulle_buffer_extract_string(), which internally calls _mulle__buffer_extract_string (lines 218-227 in src/mulle--buffer.c).
  • Null-termination is guaranteed by mulle_buffer_make_string() (backed by lines 196-215 in src/mulle--buffer.c), though extraction functions typically invoke this automatically.
  • Memory ownership transfers to the caller upon extraction; you must release the string with mulle_free() to avoid leaks.
  • The mulle_buffer_do_string macro (lines 640-679 in src/mulle-buffer.h) provides a safe, scope-bound alternative that automates cleanup.
  • Custom allocators are supported via mulle_buffer_do_allocator for specialized memory management requirements.

Frequently Asked Questions

What is the difference between mulle_buffer_make_string and mulle_buffer_extract_string?

mulle_buffer_make_string() ensures the buffer contains a null terminator but keeps the data attached to the buffer structure, whereas mulle_buffer_extract_string() first ensures null termination, then shrinks the allocation to fit exactly, detaches it from the buffer, and returns an independent pointer. Only the extracted pointer represents a persistent string that survives buffer destruction.

Do I need to call mulle_buffer_make_string before mulle_buffer_extract_string?

No, calling mulle_buffer_make_string() explicitly is optional. According to the implementation in src/mulle--buffer.c at lines 218-227, mulle_buffer_extract_string() internally invokes the make_string logic to guarantee null termination before performing the extraction and allocation shrink.

How do I free a persistent string created with mulle-buffer?

Strings returned by mulle_buffer_extract_string() are allocated using the buffer's allocator (typically the default allocator). You must release this memory using mulle_free() when the string is no longer needed to prevent memory leaks.

Can I create persistent strings with custom memory allocators?

Yes. Use the mulle_buffer_do_allocator macro (lines 731-764 in src/mulle-buffer.h) to specify a custom struct mulle_allocator. The extraction process will use your allocator for both the temporary buffer operations and the final persistent string allocation, ensuring consistent memory management throughout the lifecycle.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →