# Can mulle-buffer Be Used as a Stream? C Stream Implementation Guide

> Discover how mulle-buffer implements C stream functionality with read write cursors seek support and automatic flushing Learn to use it as a full featured input output stream

- Repository: [mulle-c/mulle-buffer](https://github.com/mulle-c/mulle-buffer)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Yes, mulle-buffer implements a byte-stream abstraction with read/write cursor mechanics, seek support, and automatic flushing capabilities that allow it to function as a full-featured input and output stream in C.**

The `mulle-c/mulle-buffer` library provides a dynamic memory buffer that doubles as a type-safe, allocator-aware stream implementation. Unlike static arrays or C strings, this library treats the underlying byte array as a linear sequence with a movable cursor, enabling both sequential consumption and production of data. According to the source code in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h), the buffer maintains a `_curr` pointer that tracks the current read/write position, giving it the same logical capabilities as a `FILE*` stream but with optional stack allocation and flexible memory management.

## How mulle-buffer Implements Stream Semantics

### The Stream Cursor Architecture

At the core of mulle-buffer's stream capability is the `_curr` pointer maintained in `struct mulle__buffer` (defined in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h)). This cursor marks the current read/write position within the underlying storage, while `_storage` points to the allocation start and `_sentinel` marks the boundary. The public API forwards seek operations to internal implementations: `mulle_buffer_set_seek` calls `_mulle__buffer_set_seek`, and `mulle_buffer_get_seek` calls `_mulle__buffer_get_seek`, enabling standard `SEEK_SET`, `SEEK_CUR`, and `SEEK_END` semantics.

### Input Stream Operations

For reading, the library provides `mulle_buffer_get_char`, `mulle_buffer_get_byte`, and `mulle_buffer_get_data`. These functions read from the `_curr` position and automatically advance the cursor, mimicking the behavior of `fgetc()` and `fread()`. This architecture allows the buffer to function as an input stream for parsing protocols or tokenizing data without modifying the underlying storage.

### Output Stream Operations

Write operations including `mulle_buffer_add_byte`, `mulle_buffer_add_string`, and `mulle_buffer_advance` write sequentially at the `_curr` position. When using a **flexible buffer**, the storage automatically expands through the associated allocator when capacity is exceeded, similar to how a dynamic file stream grows to accommodate new data.

## Reading from mulle-buffer as an Input Stream

To use mulle-buffer as a read-only input stream, initialize it with static storage and seek to specific positions before reading. The `mulle_buffer_init_inflexable_with_static_bytes` function treats the source array as fixed-capacity stream storage.

```c
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>

void demo_read_stream(void)
{
    static const char src[] = "Mulle‑Buffer demo\n";
    struct mulle_buffer buf;

    /* Initialise a non‑growable buffer that uses the static array as storage */
    mulle_buffer_init_inflexable_with_static_bytes(&buf,
                                                   (void *)src,
                                                   sizeof(src));

    /* Move to the 7th byte ('B') */
    mulle_buffer_set_seek(&buf, SEEK_SET, 7);

    int ch;
    while ((ch = mulle_buffer_get_char(&buf)) != '\n' && ch != EOF)
        putchar(ch);                /* prints “Buffer” */

    mulle_buffer_done(&buf);
}

```

*Key implementation details*: The `set_seek` function positions the cursor at byte 7, and `get_char` reads sequentially until newline. Because the buffer is initialized as **inflexible**, it cannot grow beyond the static source array, ensuring read-only safety for the input stream.

## Writing to mulle-buffer as an Output Stream

For output streaming, the `mulle_buffer_do` macro creates a stack-allocated flexible buffer that automatically manages memory and cleanup. This pattern provides RAII-style resource management for stream writing.

```c
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>

void demo_write_stream(void)
{
    /* The macro creates a stack‑based buffer that grows automatically. */
    mulle_buffer_do (buf)
    {
        mulle_buffer_add_string(&buf, "Hello, ");
        mulle_buffer_add_string(&buf, "world!");
        /* Append a newline */
        mulle_buffer_add_char(&buf, '\n');

        /* Print the accumulated data */
        printf("%s", mulle_buffer_get_string(&buf));
    } /* <- automatically calls mulle_buffer_done(&buf) */
}

```

*Key implementation details*: The `mulle_buffer_do` macro declares the buffer, initializes it with the default allocator, and ensures `mulle_buffer_done` is called at block exit to free resources. The `add_string` and `add_char` functions write sequentially at the current cursor position, advancing `_curr` automatically.

## Automatic Flushing with mulle_flushablebuffer

The companion type **`mulle_flushablebuffer`** (declared in [`src/mulle-flushablebuffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-flushablebuffer.h) and implemented in [`src/mulle-flushablebuffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-flushablebuffer.c)) transforms a standard buffer into a write-only output stream that automatically flushes contents to a user-supplied callback. The flusher function (typically `fwrite` or a custom writer) is invoked when internal storage fills up via `_mulle_flushablebuffer_flush` or when the buffer is destroyed via `mulle_flushablebuffer_done`.

```c
#include <mulle-buffer/mulle-buffer.h>
#include <stdio.h>

void dump_to_file(FILE *fp, const void *bytes, size_t len)
{
    struct mulle_flushablebuffer fbuf;
    struct mulle_buffer *buf;

    /* 1 KB local storage; when full it will be flushed via fwrite */
    mulle_flushablebuffer_init(&fbuf,
                               /*storage*/ (char[1024]){0},
                               1024,
                               (mulle_flushablebuffer_flusher_t *)fwrite,
                               fp);

    buf = (struct mulle_buffer *)&fbuf;   /* treat as a normal buffer */
    mulle_buffer_add_string(buf, "---\n");
    mulle_buffer_hexdump(buf, bytes, len, 0, mulle_buffer_hexdump_default);
    mulle_buffer_add_string(buf, "---\n");

    /* Ensure any remaining data is written */
    mulle_flushablebuffer_done(&fbuf);
}

```

*Key implementation details*: The `mulle_flushablebuffer_init` function accepts a static storage array (1KB in this example), a flusher callback, and a context pointer (the `FILE*`). Data is written using the standard `mulle_buffer_*` API; when the 1024-byte capacity is exhausted, the buffer automatically flushes to the file via `fwrite`. The final `done` call ensures no data remains in the internal buffer.

## Summary

- **Byte-stream abstraction**: mulle-buffer implements a `_curr` cursor mechanism in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) that supports sequential read/write operations identical to `FILE*` streams.
- **Bidirectional I/O**: The library supports both input streaming (via `mulle_buffer_get_*` functions) and output streaming (via `mulle_buffer_add_*` functions) with full seek support for `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`.
- **Flexible and inflexible modes**: Use `mulle_buffer_do` for auto-growing output streams, or `mulle_buffer_init_inflexable_with_static_bytes` for fixed-size input streams.
- **Automatic flushing**: The `mulle_flushablebuffer` type in [`src/mulle-flushablebuffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-flushablebuffer.c) adds write-behind buffering capabilities, automatically calling a user-provided flusher when storage limits are reached.
- **Type-safe and allocator-aware**: Unlike raw `FILE*` pointers, mulle-buffer streams are type-safe and integrate with custom memory allocators while supporting stack allocation via convenience macros.

## Frequently Asked Questions

### Does mulle-buffer support random access seeking like fseek?

Yes, mulle-buffer implements full POSIX-compatible seek semantics through `mulle_buffer_set_seek` and `mulle_buffer_get_seek`. According to the implementation in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h), these functions forward to `_mulle__buffer_set_seek` which supports `SEEK_SET` (absolute position), `SEEK_CUR` (relative offset), and `SEEK_END` (offset from end) operations, allowing random access within the buffer's storage limits.

### What distinguishes mulle_flushablebuffer from standard mulle_buffer?

`mulle_flushablebuffer` is a specialized wrapper that adds automatic flushing behavior to the base buffer implementation. While a standard `mulle_buffer` stores all data in memory until manually extracted, a flushable buffer invokes a user-supplied callback (such as `fwrite`) whenever its internal storage fills up or when `mulle_flushablebuffer_done` is called. This makes it suitable for streaming large datasets to files or network sockets without loading the entire dataset into RAM.

### Can mulle-buffer integrate with existing FILE* based APIs?

Yes, through the flushable buffer pattern demonstrated in [`src/mulle-flushablebuffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-flushablebuffer.h). You can initialize a `mulle_flushablebuffer` with `fwrite` as the flusher callback and a `FILE*` pointer as the context. This allows the buffer to function as an intermediate stream that automatically writes to standard C file handles while maintaining the performance benefits of buffered I/O.

### Is the stream implementation allocator-aware for memory-constrained environments?

Yes, mulle-buffer streams are fully allocator-aware. The flexible buffer mode used in `mulle_buffer_do` accepts a custom allocator for automatic growth, while inflexible modes use pre-allocated static storage suitable for embedded systems. This design allows the same stream API to function in both high-performance server environments and memory-constrained microcontroller applications without code changes.