mulle_buffer vs mulle_flushablebuffer: Key Differences in the mulle-buffer Library

mulle_buffer is a flexible, growable memory container for general-purpose data construction, while mulle_flushablebuffer is a fixed-size, stream-oriented extension that automatically flushes contents to an external sink via a callback function when the buffer fills.

The mulle-buffer library provides two primary data structures for memory management in C: mulle_buffer and mulle_flushablebuffer. While both serve as containers for binary or text data, they target fundamentally different use cases and exhibit distinct architectural behaviors. Understanding the difference between mulle_buffer and mulle_flushablebuffer is essential for selecting the appropriate tool for string construction versus stream-oriented I/O operations.

Core Architectural Differences

Structure and Inheritance

In src/mulle-buffer.h (lines 70-77), struct mulle_buffer is defined as a thin wrapper around the internal mulle__buffer structure plus an allocator field. This design provides the foundation for dynamic memory management operations.

In contrast, struct mulle_flushablebuffer defined in src/mulle-flushablebuffer.h (lines 73-77) embeds the entire mulle_buffer layout via the MULLE_BUFFER_BASE macro. It extends the base structure with three additional fields: a flusher callback, a user-data pointer, and a flushed-byte counter.

Memory Growth Models

mulle_buffer operates as a flexible container. It can grow or shrink on demand through functions like mulle_buffer_grow() and mulle_buffer_set_length(), automatically reallocating memory as needed to accommodate incoming data.

mulle_flushablebuffer is inflexible by design. The underlying storage has a fixed size established at creation. When the buffer reaches capacity, it does not reallocate; instead, it invokes the user-supplied flusher function to drain contents to an external sink.

Functional Characteristics

API Flags and Access Modes

mulle_buffer supports multiple operational modes via bitwise flags: MULLE_BUFFER_IS_FLEXIBLE (default behavior), optionally combined with MULLE_BUFFER_IS_READONLY, MULLE_BUFFER_IS_WRITEONLY, or MULLE_BUFFER_IS_TEXT.

mulle_flushablebuffer initializes with a constrained flag set defined by the MULLE_FLUSHABLEBUFFER_TYPE macro: MULLE_BUFFER_IS_INFLEXIBLE | MULLE_BUFFER_IS_FLUSHABLE | MULLE_BUFFER_IS_WRITEONLY. This enforces write-only, stream-oriented semantics suitable for output streams like files or sockets.

Data Retrieval vs. Automatic Flushing

With mulle_buffer, data retrieval happens explicitly through extraction functions like mulle_buffer_extract_data() or mulle_buffer_get_string(). The caller manually manages when and how to access the buffered contents.

mulle_flushablebuffer automates output through its flushing mechanism. The flusher callback conforms to the signature size_t (*)(void *buf, size_t one, size_t len, void *userinfo). Explicit flushing occurs via mulle_flushablebuffer_flush(), while mulle_flushablebuffer_done() performs an implicit flush during destruction. This design eliminates the need to hold large datasets in memory before writing.

Practical Usage Examples

Building Strings with mulle_buffer

Use mulle_buffer when assembling C-strings or binary blobs that require dynamic resizing. The mulle_buffer_create_default() function initializes the buffer with the default allocator.

#include "mulle-buffer.h"

int main(void)
{
    struct mulle_buffer *buf = mulle_buffer_create_default();
    mulle_buffer_add_string(buf, "Hello, ");
    mulle_buffer_add_string(buf, "world!");
    printf("%s\n", mulle_buffer_get_string(buf));
    mulle_buffer_destroy(buf);
    return 0;
}

The implementation of mulle_buffer_add_string() resides in src/mulle-buffer.c, handling automatic growth when the internal storage fills.

Streaming to Files with mulle_flushablebuffer

For stream-oriented output, use the mulle_flushablebuffer_do_FILE macro defined in src/mulle-flushablebuffer.h (lines 104-124). This creates a stack-allocated buffer that flushes to a FILE* when full.

#include "mulle-flushablebuffer.h"

int main(void)
{
    mulle_flushablebuffer_do_FILE(buf, stdout)
    {
        struct mulle_buffer *b = buf;
        mulle_buffer_add_string(b, "Line 1\n");
        mulle_buffer_add_string(b, "Line 2\n");
    }
    return 0;
}

When the scope ends, the macro invokes mulle_flushablebuffer_done(), which flushes remaining data to stdout via fwrite.

Implementing Custom Flushers

Define a custom flusher to direct output to arbitrary sinks such as network sockets. The creation function mulle_flushablebuffer_create() (declared at lines 80-86 in src/mulle-flushablebuffer.h) accepts your callback and userinfo pointer.

#include "mulle-flushablebuffer.h"
#include <sys/socket.h>

static size_t socket_flusher(void *buf, size_t one, size_t len, void *userinfo)
{
    int fd = *(int *)userinfo;
    return send(fd, buf, len, 0);
}

int main(void)
{
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    struct mulle_flushablebuffer *fb =
        mulle_flushablebuffer_create(1024, socket_flusher, &sock, NULL);

    struct mulle_buffer *b = mulle_flushablebuffer_as_buffer(fb);
    mulle_buffer_add_string(b, "Hello socket!\n");
    
    mulle_flushablebuffer_done(fb);
    close(sock);
    return 0;
}

The conversion macro mulle_flushablebuffer_as_buffer() (lines 43-48) allows treating the flushable buffer as a standard mulle_buffer for writing operations.

Summary

  • mulle_buffer provides flexible, growable storage for general-purpose data construction with manual extraction.
  • mulle_flushablebuffer provides fixed-size, write-only storage that automatically flushes to external sinks via callbacks.
  • mulle_flushablebuffer inherits from mulle_buffer using MULLE_BUFFER_BASE, extending it with flusher machinery rather than duplicating functionality.
  • Use mulle_buffer for string building and data assembly; use mulle_flushablebuffer for streaming I/O to files, sockets, or custom output devices.

Frequently Asked Questions

Can I read from a mulle_flushablebuffer like I do with mulle_buffer?

No. mulle_flushablebuffer is strictly write-only (MULLE_BUFFER_IS_WRITEONLY). It is designed exclusively for output streams. If you need to read data back, use mulle_buffer which supports read-only, write-only, or read/write modes.

How do I retrieve data from a mulle_flushablebuffer after writing?

You do not extract data directly. Instead, the buffer pushes content to your flusher callback automatically when full or explicitly via mulle_flushablebuffer_flush(). For the final remaining bytes, call mulle_flushablebuffer_done() to trigger the last flush operation.

Can I convert a mulle_buffer into a mulle_flushablebuffer?

No safe conversion exists from mulle_buffer to mulle_flushablebuffer. However, you can safely cast a mulle_flushablebuffer pointer to a mulle_buffer pointer using mulle_flushablebuffer_as_buffer() (defined in src/mulle-flushablebuffer.h). This allows reuse of the standard buffer API for write operations while maintaining the flushable behavior.

Which type should I use for logging large amounts of data?

Use mulle_flushablebuffer for large-scale logging. Its fixed-size design prevents memory exhaustion by writing chunks to disk or stdout as the buffer fills, whereas mulle_buffer would continue allocating memory until the system runs out of RAM or the process terminates.

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 →