On Tuesday, 25 June 2013 at 22:50:55 UTC, H. S. Teoh wrote:
And maybe (b) can be implemented by making gc_alloc / gc_free
overridable function pointers? Then we can override their values and use scope guards to revert them back to the values they were before.

Yea, I was thinking this might be a way to go. You'd have a global (well, thread-local) allocator instance that can be set and reset through stack calls.

You'd want it to be RAII or delegate based, so the scope is clear.

with_allocator(my_alloc, {
     do whatever here
});


or

{
   ChangeAllocator!my_alloc dummy;

   do whatever here
} // dummy's destructor ends the allocator scope


I think the former is a bit nicer, since the dummy variable is a bit silly. We'd hope that delegate can be inlined.



But, the template still has a big advantage: you can change the type. And I think that is potentially enormously useful.



Another question is how to tie into output ranges. Take std.conv.to.

auto s = to!string(10); // currently, this hits the gc

What if I want it to go on a stack buffer? One option would be to rewrite it to use an output range, and then call it like:

char[20] buffer;
auto s = to!string(10, buffer); // it returns the slice of the buffer it actually used

(and we can do overloads so to!string(10, radix) still works, as well as to!string(10, radix, buffer). Hassle, I know...)

Naturally, the default argument is to use the 'global' allocator, whatever that is, which does nothing special.



The fun part is the output range works for that, and could also work for something like this:

struct malloced_string {
    char* ptr;
    size_t length;
    size_t capacity;
    void put(char c) {
        if(length >= capacity)
           ptr = realloc(ptr, capacity*2);
        ptr[length++] = c;
    }

    char[] slice() { return ptr[0 .. length]; }
    alias slice this;
    mixin RefCounted!this; // pretend this works
}


{
   malloced_string str;
   auto got = to!string(10, str);
} // str is out of scope, so it gets free()'d. unsafe though: if you stored a copy of got somewhere, it is now a pointer to freed memory. I'd kinda like language support of some sort to help mitigate that though, like being a borrowed pointer that isn't allowed to be stored, but that's another discussion.


And that should work. So then what we might do is provide these little output range wrappers for various allocators, and use them on many functions.

So we'd write:

import std.allocators;
import std.range;

// mallocator is provided in std.allocators and offers the goods
OutputRange!(char, mallocator) str;

auto got = to!string(10, str);



What's nice here is the output range is useful for more than just allocators. You could also to!string(10, my_file) or a delegate, blah blah blah. So it isn't too much of a burden, it is something you might naturally use anyway.

Also, we may have the problem of the wrong allocator
being used to free the object.

Another reason why encoding the allocator into the type is so nice. For the minimal D I've been playing with, the idea I'm running with is all allocated memory has some kind of special type, and then naked pointers are always assumed to be borrowed, so you should never store or free them.

auto foo = HeapArray!char(capacity);

void bar(char[] lol){}

bar(foo); // allowed, foo has an alias this on slice

// but....

struct A {
char[] lol; // not allowed, because you don't know when lol is going to be freed
}


foo frees itself with refcounting.

Reply via email to