# mulle_buffer_do_string Convenience Macros: RAII String Building in C

> Discover mulle_buffer_do_string macros for RAII string building in C. Effortlessly manage temporary string builders and extract C-strings with automatic resource cleanup.

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

---

**The `mulle_buffer_do_string` convenience macros provide block-scoped, automatic resource management for temporary string builders, extracting a finished C-string and finalizing the buffer when the code block exits.**

The `mulle_buffer_do_string` convenience macros in the [mulle-c/mulle-buffer](https://github.com/mulle-c/mulle-buffer) repository eliminate manual memory management when constructing dynamic C-strings. These helper macros wrap `struct mulle_buffer` operations in a declarative, for-loop-based scope that guarantees cleanup—even when you exit early with `break`—making them ideal for safe, temporary string concatenation.

## What Are mulle_buffer_do_string Convenience Macros?

`mulle_buffer_do_string` is a **block-scoped helper macro** defined in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) that treats a `struct mulle_buffer` as a temporary string builder with automatic extraction. The macro accepts three parameters: the buffer identifier name, an allocator (or `NULL` for the default), and the output variable that receives the final C-string.

When execution enters the macro’s code block, it instantiates a `struct mulle_buffer` either on the stack or using the supplied allocator. Inside the block, you call standard `mulle_buffer_*` functions such as `mulle_buffer_add_string()` or `mulle_buffer_add_c_string()` to append data. When the block terminates—normally or via `break`—the macro automatically calls `mulle_buffer_extract_string()` (implemented in [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c)) to transfer ownership of the constructed string to your variable and finalizes the buffer to prevent leaks.

This pattern implements **resource-acquisition-is-initialization (RAII)** semantics in C: the macro creates the resource, you use it, and the macro’s hidden cleanup code releases it.

## How the Macro Works (Source Implementation)

The macro’s implementation relies on a clever double `for`-loop construct that ensures single execution and safe cleanup. Here is the actual definition from [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h):

```c
#define mulle_buffer_do_string( name, allocator, s)               \
   for( struct mulle_buffer                                       \
           name ## __storage = MULLE_BUFFER_DATA( allocator),     \

           *name = &name ## __storage,                            \

           *name ## __i = NULL;                                   \

        \
        s = (name ## __i)                                         \

               ? mulle_buffer_extract_string( &name ## __storage) \

               : NULL,                                            \
        ! name ## __i;                                            \

        \
        name ## __i = (void *) 0x1                                \

      )                                                           \
   \
   for( int  name ## __j = 0;    /* break protection */        \

        name ## __j < 1;                                       \

        name ## __j++)

```

**Execution flow:**

- **First `for` loop** – Initializes `name ## __storage` using `MULLE_BUFFER_DATA(allocator)`, creates a pointer alias `name`, and declares a sentinel `name ## __i`. The condition `! name ## __i` is true initially, so the body executes once.

- **String extraction** – After the inner block completes, the first loop’s iteration expression assigns `s` by calling `mulle_buffer_extract_string(&name ## __storage)`, which also frees the buffer’s internal storage. It then sets `name ## __i` to a non-NULL sentinel, causing the outer loop to terminate.

- **Second `for` loop** – Provides a scoped block that runs exactly once, allowing you to use `break` safely without skipping the cleanup code in the outer loop’s iteration section.

## Related Convenience Macros

The `mulle_buffer_do_string` macro builds upon a family of buffer-scoping helpers, all located in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h):

| Macro | Purpose | Key Difference |
|-------|---------|----------------|
| `mulle_buffer_do` | Creates a temporary buffer with the default allocator | No automatic string extraction; you manually finalize |
| `mulle_buffer_do_allocator` | Creates a buffer with a custom allocator | Accepts an explicit `struct mulle_allocator*` parameter |
| `mulle_buffer_do_flexible` / `mulle_buffer_do_filled` | Backs the buffer with user-supplied static storage | Avoids heap allocation for small, fixed-size workloads |
| `mulle_buffer_do_string` | **Adds automatic C-string extraction** | Assigns the result of `mulle_buffer_extract_string()` to your variable on exit |

## Practical Code Examples

### Basic String Building with Default Allocator

The most common use case passes `NULL` for the allocator to use the system default. This example is adapted from the test suite in [`test/buffer/do-string.c`](https://github.com/mulle-c/mulle-buffer/blob/main/test/buffer/do-string.c):

```c
#include "mulle-buffer.h"

void example(void)
{
    char *result;
    mulle_buffer_do_string(buf, NULL, result) {
        mulle_buffer_add_string(buf, "Mulle is ");
        mulle_buffer_add_c_string(buf, "awesome!");
    }
    printf("%s\n", result);   // prints: Mulle is awesome!
    mulle_free(result);
}

```

### Using Custom Allocators

When you need control over memory allocation strategies, provide a custom `struct mulle_allocator*`. Remember to free the resulting string with the same allocator:

```c
struct mulle_allocator *myalloc = mulle_allocator_create(...);

char *out;
mulle_buffer_do_string(buf, myalloc, out) {
    mulle_buffer_add_c_string(buf, "custom-allocator ");
    mulle_buffer_add_string(buf, "example");
}
/* out must be freed with the same allocator */
mulle_allocator_free(myalloc, out);
mulle_allocator_destroy(myalloc);

```

### Early Exit with `break`

The macro’s dual-loop structure safely handles early exits. The cleanup code in the outer loop’s iteration section always runs, ensuring `buf` is finalized even when you `break`:

```c
char *txt;
mulle_buffer_do_string(buf, NULL, txt) {
    if (some_condition())
        break;                     // skips further appends, still safe
    mulle_buffer_add_string(buf, "won't be executed");
}
if (txt) {
    printf("%s\n", txt);
    mulle_free(txt);
} else {
    puts("No string generated");
}

```

## Summary

- `mulle_buffer_do_string` is defined in [`src/mulle-buffer.h`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle-buffer.h) and provides **RAII-style string building** in C.
- The macro accepts three arguments: the buffer name, an allocator (or `NULL`), and the output string variable.
- Upon block exit, it automatically calls `mulle_buffer_extract_string()` (from [`src/mulle--buffer.c`](https://github.com/mulle-c/mulle-buffer/blob/main/src/mulle--buffer.c)) to transfer the C-string and finalize the buffer.
- The implementation uses a **double `for`-loop** to guarantee cleanup even when using `break`.
- Related macros like `mulle_buffer_do` and `mulle_buffer_do_allocator` offer flexibility for non-string or custom-allocation scenarios.

## Frequently Asked Questions

### What is the difference between `mulle_buffer_do` and `mulle_buffer_do_string`?

`mulle_buffer_do` creates a temporary buffer scope but requires you to manually call `mulle_buffer_extract_string()` or `mulle_buffer_done()` to finalize resources. `mulle_buffer_do_string` wraps this workflow by automatically extracting the C-string into your specified variable when the block ends, saving boilerplate and preventing leaks.

### How do I free the string returned by `mulle_buffer_do_string`?

The macro assigns ownership of the extracted C-string to your variable, which you must free using the same allocator passed to the macro. If you used `NULL` (the default allocator), call `mulle_free(string)`. If you used a custom allocator, use `mulle_allocator_free(allocator, string)`.

### Can I use `mulle_buffer_do_string` with a custom allocator?

Yes. Pass a pointer to a `struct mulle_allocator` as the second argument instead of `NULL`. All internal buffer operations, including the final extraction in `mulle_buffer_extract_string()`, will use your allocator. Ensure you destroy the allocator only after freeing the resulting string.

### What happens if I break out of the macro block early?

The macro’s implementation uses a nested `for` loop specifically to handle `break` statements safely. When you `break` from the inner loop, control returns to the outer loop’s iteration expression, which still executes `mulle_buffer_extract_string()` and finalizes the buffer. Thus, early exits do not leak memory or leave the buffer in an undefined state.