What is mulle_flushablebuffer and When Should You Use It?

mulle_flushablebuffer is a write-only buffer structure in the mulle-c ecosystem that automatically flushes its contents to a user-defined output callback whenever its internal storage reaches capacity, making it ideal for streaming I/O operations.

The mulle_flushablebuffer type extends the generic mulle_buffer infrastructure in the mulle-c/mulle-buffer repository to support automatic, callback-driven flushing. It allows C developers to batch write operations efficiently while delegating the actual byte transport to a custom flusher function, reducing system call overhead in high-throughput scenarios.

What is mulle_flushablebuffer?

Defined in src/mulle-flushablebuffer.h, this structure extends the base buffer with three additional fields via the MULLE_FLUSHABLEBUFFER_BASE macro (lines 61-66):

  • _flusher — a callback matching the fwrite signature: size_t (*)(void *buf, size_t one, size_t len, void *userinfo)
  • _userinfo — an opaque pointer passed to every flusher invocation (typically a FILE* or socket descriptor)
  • _flushed — a running count of bytes already flushed to the destination

The buffer type is marked as inflexible, flushable, and write-only through the flag defined at lines 84-86:

#define MULLE_FLUSHABLEBUFFER_TYPE \
    (MULLE_BUFFER_IS_INFLEXIBLE | MULLE_BUFFER_IS_FLUSHABLE | MULLE_BUFFER_IS_WRITEONLY)

The actual flush logic resides in src/mulle-flushablebuffer.c, while mulle_flushablebuffer_flush is exposed as a public inline wrapper at lines 32-38 of the header.

When to Use mulle_flushablebuffer

Choose this buffer type when you need automatic flushing without manual capacity checks. Common scenarios include:

  • Streaming to stdout or files — The buffer calls fwrite automatically when full, letting you write small chunks without per-character system call overhead.
  • Network and socket I/O — Supply a flusher that writes to a socket descriptor; the buffer feeds the socket incrementally until closure.
  • High-performance logging — Batch writes to minimize syscalls, flushing only when the buffer fills or when explicitly finalized.
  • Embedded custom streams — Because you provide the storage, you can place the buffer on the stack, in static memory, or dynamically allocate it to match surrounding code lifetimes.

Core API and Initialization Workflow

The implementation spans two primary files:

Standard usage follows four steps:

  1. Allocate storage using a static array, malloc, or stack allocation.
  2. Initialize with mulle_flushablebuffer_init for static storage or mulle_flushablebuffer_init_with_allocated_bytes for dynamic buffers.
  3. Cast to base type via mulle_flushablebuffer_as_buffer to access standard mulle_buffer_* write helpers.
  4. Finalize with mulle_flushablebuffer_done to flush remaining bytes and release resources.

Code Examples

Stack-Allocated stdout Streaming

The mulle_flushablebuffer_do_FILE macro (lines 104-118) creates a 256-byte stack buffer that flushes to FILE* streams:

#include <mulle-flushablebuffer/mulle-flushablebuffer.h>

mulle_flushablebuffer_do_FILE( out, stdout )
{
    /* 'out' is a struct mulle_buffer * inside the block */
    mulle_buffer_add_string( out, "Hello, World!\n" );
    mulle_buffer_add_format( out, "Number: %d\n", 42 );
}
/* Automatic flush and cleanup occurs here */

Custom Socket Flusher

For network I/O, provide a flusher matching the required signature:

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

static size_t
socket_flusher( void *buf, size_t one, size_t len, void *userinfo )
{
    int fd = (int)(intptr_t)userinfo;
    ssize_t written = send( fd, buf, len, 0 );
    return written > 0 ? (size_t)written : 0;
}

int main( void )
{
    int sock = /* ... create socket ... */;
    char storage[ 1024 ];

    struct mulle_flushablebuffer fb;
    mulle_flushablebuffer_init( &fb, storage, sizeof(storage),
                                socket_flusher, (void *)(intptr_t)sock );

    struct mulle_buffer *buf = mulle_flushablebuffer_as_buffer( &fb );
    mulle_buffer_add_string( buf, "GET / HTTP/1.0\r\n\r\n" );
    
    mulle_flushablebuffer_done( &fb );   /* flush remaining bytes */
    close( sock );
    return 0;
}

Dynamic Allocation with Custom Allocator

For heap-managed buffers, use the creation API:

#include "mulle-flushablebuffer.h"

struct mulle_allocator *alloc = mulle_std_allocator;   /* default allocator */

struct mulle_flushablebuffer *fb =
    mulle_flushablebuffer_create( 4096, fwrite, stdout, alloc );

struct mulle_buffer *buf = mulle_flushablebuffer_as_buffer( fb );
mulle_buffer_add_format( buf, "Dynamic buffer test: %d\n", 100 );

mulle_flushablebuffer_done( fb );   /* flushes and frees memory */

Summary

  • mulle_flushablebuffer is a write-only, automatically flushing buffer built on top of mulle_buffer infrastructure.
  • It adds _flusher, _userinfo, and _flushed fields to track output callbacks and byte counts.
  • Use it for streaming I/O, socket writes, and high-throughput logging to minimize system calls.
  • The API supports stack allocation via macros, manual initialization with custom flushers, and dynamic creation with allocators.
  • Always call mulle_flushablebuffer_done to ensure final bytes are flushed and resources are released.

Frequently Asked Questions

What is the difference between mulle_flushablebuffer and a regular mulle_buffer?

A standard mulle_buffer is a general-purpose growable or fixed-size byte array. In contrast, mulle_flushablebuffer is specifically write-only and inflexible, featuring an automatic flush mechanism that invokes a callback when the buffer fills. This design separates the concerns of buffering and I/O transport.

Can I use mulle_flushablebuffer for reading data?

No. The buffer type explicitly sets the MULLE_BUFFER_IS_WRITEONLY flag (defined in src/mulle-flushablebuffer.h), preventing read operations. Attempting to read from a flushable buffer would violate its design contract and likely cause assertion failures or undefined behavior.

What happens if my flusher callback returns fewer bytes than requested?

The flusher signature matches fwrite, returning the number of items successfully written. If the callback returns fewer bytes than the buffer attempted to flush, the buffer implementation in src/mulle-flushablebuffer.c handles the short write appropriately, though you should ensure your flusher either completes the write or signals an error condition to prevent data loss.

Do I need to call mulle_flushablebuffer_flush manually?

You can invoke mulle_flushablebuffer_flush explicitly via the inline wrapper at lines 32-38 of the header if you need to force a flush mid-operation. However, the buffer calls this automatically when capacity is exceeded, and mulle_flushablebuffer_done performs a final flush during cleanup, so manual intervention is only required for specific synchronization needs.

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 →