irst I'd like to say that I don't really like (or rather use) Appender because it always allocates (at least an internal Data instance) even when I provide my own buffer. I mean, why would I use Appender if it still allocates? Okay, you have to store a reference to an internal representation so that Appender would feel like a reference type. I'm not sure it's worth the trade-off, and as such I defined and use my own set of primitives that don't allocate when a buffer is provided:

void put(T)(ref T[] array, ref size_t offset, const(T) value)
{
    ensureCapacity(array, offset + 1);
    array[offset++] = value;
}

void put(T)(ref T[] array, ref size_t offset, const(T)[] value)
{
    // Same but for an array
}

void ensureCapacity(ref char[] array, size_t minCapacity)
{
   // ...
}

And all that functions that use an optional buffer have a signature like this:

void foo(ubyte[] buffer = null);

Back to my original question, can we mimick a reference behavior with a struct? I thought why not until I hit this bug:

import std.array;
import std.stdio;

void append(Appender!(string) a, string s)
{
        a.put(s);
}

void main()
{
        Appender!(string) a;
        string s = "test";
        
        append(a, s); // <
        
        writeln(a.data);        
}

I'm passing an appender by value since it's supposed to have a reference type behavior and passing 4 bytes by reference is an overkill.

However, the code above doesn't work for a simple reason: structs lack default ctors. As such, an appender is initialized to null internally, when I call append a copy of it gets initialized (lazily), but the original one remains unchanged. Note that if you append to appender at least once before passing by value, it will work. But that's sad. Not only it allocates when it shouldn't, I also have to initialize it explicitly!

I think far better solution would be to make it non-copyable.

TL;DR Reference semantic mimicking with a struct without default ctors is unreliable since you must initialize your object lazily. Moreover, you have to check that you struct is not initialized yet every single function call, and that's error prone and bad for code clarity and performance. I'm opposed of that practice.

Reply via email to