Thread overview
Stack-based @nogc dynamic array
May 16, 2019
Marco de Wild
May 16, 2019
Adam D. Ruppe
May 17, 2019
Marco de Wild
May 16, 2019
Kagamin
May 17, 2019
Marco de Wild
May 17, 2019
Kagamin
May 17, 2019
Mike Franklin
May 16, 2019
Hey all,

I want to create a small collection of items to store intermediate results of a calculation. It runs on a background thread, so it does not need to be the most efficient implementation. However, I want to prevent my background thread introducing a stop-the-world garbage collection.
In my program (rules and an AI for a mahjong game), the collection size is at most 14 (tiles in hand). I figured it would be the most simple to just keep it stack based. MY first attempt looks like:
```
struct NoGcArray(size_t maxSize, T)
{
    private T[maxSize] _buffer;
    private size_t _length;
    size_t length() pure const @nogc nothrow
    {
        return _length;
    }

    void opOpAssign(string op)(T element) pure @nogc nothrow
    in(_length < maxSize, "Cannot append if the buffer is fully filled.")
    {
        static if(op == "~")
        {
            _buffer[_length] = element;
            ++_length;
        }
        else
        {
            static assert(false, "Only concatenation supported");
        }
    }

    T opIndex(size_t index)
    in(index < _length, "Cannot access index greater than length")
    {
        return _buffer[index];
    }

    auto range() pure const @nogc nothrow
    {
        return Range(this);
    }

    alias range this;

    static struct Range
    {
        this(NoGcArray src)
        {
            _src = src;
        }
        private NoGcArray _src;
        private size_t _index;

        T front() pure @nogc nothrow
        {
            return _src[_index];
        }

        void popFront() pure @nogc nothrow
        {
            _index++;
        }

        bool empty() pure @nogc nothrow
        {
            return _src._length <= _index;
        }
    }
}
```
However,
```
unittest
{
    import std.algorithm : sum, map;
    import fluent.asserts;
    NoGcArray!(4, int) array;
    array ~= 420;
    array ~= 42;
    array.map!(x => x*2).sum.should.equal(924);
}
```
fails. The test will run in an infinite loop. After some digging, I realise that the `alias this` is foiling with my plan rather than helping it. Is there a recommended way to achieve
- std.algorithm functions should Just Work (tm)
- appending as if it were a dynamic array
- preferably index-based access and .length should also work.

Is there a dub package that achieves this? Or are there any tips to roll my own implementation?
May 16, 2019
I think you have overcomplicated something quite simple.

int[4] buffer;
int bufferLength;

buffer[bufferLength++] = item_to_append;
buffer[bufferLength++] = item_to_append;

int[] slice = buffer[0 .. bufferLength];

// you can use slice to any std.algorithm calls etc
// just remember it is on the stack so don't store it beyond a function call
May 16, 2019
Try mach.d https://github.com/pineapplemachine/mach.d it uses explicit range accessors for iteration.
May 17, 2019
Thank you both for the quick replies.

On Thursday, 16 May 2019 at 12:55:34 UTC, Kagamin wrote:
> Try mach.d https://github.com/pineapplemachine/mach.d it uses explicit range accessors for iteration.

Thank you. I've looked into it, and it appears to be quite a big library. I've looked into mach.collect and mach.range, but I didn't manage to find what I was looking for (a simple array data structure). Do you recommend to look into any specific module?
May 17, 2019
On Thursday, 16 May 2019 at 12:45:03 UTC, Adam D. Ruppe wrote:
> I think you have overcomplicated something quite simple.
>
> int[4] buffer;
> int bufferLength;
>
> buffer[bufferLength++] = item_to_append;
> buffer[bufferLength++] = item_to_append;
>
> int[] slice = buffer[0 .. bufferLength];
>
> // you can use slice to any std.algorithm calls etc
> // just remember it is on the stack so don't store it beyond a function call

Thanks. It's really a lot simpler than I thought. It's slightly error prone (i.e., the code doesn't work if I use ++bufferLength), but its simplicity might be worth the trade-off.
May 17, 2019
On Thursday, 16 May 2019 at 12:16:26 UTC, Marco de Wild wrote:

> Or are there any tips to roll my own implementation?

I took a stab at it:

---
@nogc:
nothrow:
pure:

struct NoGcArray(size_t maxSize, T)
{
    private T[maxSize] _buffer;
    private T[] _slice;
    private size_t _frontIndex;

    size_t length() const
    {
        return _slice.length;
    }

    void opOpAssign(string op)(T element)
    {
        static if(op == "~")
        {
            _buffer[_frontIndex + length] = element;
            _slice = _buffer[_frontIndex..(length + 1)];
        }
        else
        {
            static assert(false, "Only concatenation supported");
        }
    }

    T opIndex(size_t index) const
    {
        return _slice[index];
    }

    T front() const
    {
        return _slice[0];
    }

    void popFront()
    {
        _frontIndex++;
        _slice = _slice[1..$];
    }

    bool empty() const
    {
        return _slice.length == 0;
    }
}

void main()
{
    import std.algorithm : sum, map;
    NoGcArray!(4, int) array;
    array ~= 420;
    array ~= 42;
    assert(array.map!(x => x*2).sum == 924);
}
---

Mike
May 17, 2019
On Friday, 17 May 2019 at 06:58:54 UTC, Marco de Wild wrote:
> Thank you. I've looked into it, and it appears to be quite a big library. I've looked into mach.collect and mach.range, but I didn't manage to find what I was looking for (a simple array data structure). Do you recommend to look into any specific module?

You use your array with math.range, but without alias this, it uses explicit accessor `asrange` for iteration.