Interesting PRs: bringing type system legitimacy to shared allocators

Stanislav Blinov via Digitalmars-d digitalmars-d at puremagic.com
Sun Apr 30 17:43:22 PDT 2017


On Sunday, 30 April 2017 at 21:43:26 UTC, Andrei Alexandrescu 
wrote:
> On 04/27/2017 07:35 PM, Stanislav Blinov wrote:
>> IAllocator is too high level an interface, it doesn't carry any
>> information as to what type of memory it can allocate (so we 
>> can only
>> assume unshared), and does or does it not use GC (so we can 
>> only assume
>> GC).
>
> Initially all fresh memory is unshared. Whether or not the user 
> subsequently shares it is of no consequence to the allocator.

Why would we need any ISharedAllocator then? If we're to force 
users to rely on *documentation* whether or not they can cast 
allocated memory to shared, there is no point in static checks, 
they'll only get in the way of user's attempts to try and stitch 
together the pieces. They're on their own anyway at that point.

>> If we are to devise types with allocators as members instead 
>> of type
>> arguments, we need the additional information. Better to catch 
>> invalid
>> assignment at compile time than to chase down how a stack 
>> allocator from
>> one thread ended up in another.
>
> A pass through the root allocators (Mallocator, GCAllocator 
> etc) figuring out what attributes could be meaningfully 
> attached would be welcome. The rest would rely on inference.

If we're type-erasing allocators, we have to have the ability to 
statically specify what traits we're interested in (i.e. *don't* 
want to erase). Otherwise, inference will not be of any help.
Consider an example, inspired by the discussion of Atila's 
automem library:

import std.experimental.allocator;
import std.algorithm.mutation : move;

// Allocator is not a type parameter
struct SmartPtr(T)
{
     // we can infer all we need form A...
     this(A, Args...)(A alloc, auto ref Args args)
     if (isAllocatorImpl!A)
     {
         // ...but we immediately throw the inference away:
         allocator_ = alloc;
         block_ = cast(Block[])allocator_.allocate(Block.sizeof);
         // SmartPtr logic snipped...
     }

     ~this()
     {
         // ...SmartPtr logic snipped
         allocator_.deallocate(block_);
     }

private:
     struct Block
     {
         size_t refCount;
         void[T.sizeof] payload;
     }

     Block[] block_;
     IAllocator allocator_;
}

struct Data {
     ~this() @nogc { /*...*/ }
     // the rest of implementation is @nogc as well
}

struct MyType
{
     // won't compile: IAllocator's methods aren't @nogc,
     // so SmartPtr's dtor isn't either
     this(SmartPtr!Data data) @nogc
     {
         data_ = move(data);
     }

private:
     SmartPtr!Data data_;
}

Obviously, IAllocator is not the tool for the job here. This 
calls for something like this:

enum AllocTraits
{
     none  = 0x00,
     nogc  = 0x01,
     share = 0x02
}

alias AllocatorInterface(AllocTraits) = // ???

struct SmartPtr(T, AllocTraits traits = AllocTraits.none)
{
     this(A, Args...)(A alloc, auto ref Args args)
     {
         // this should not compile if A isn't compatible
         // with `traits`:
         allocator_ = alloc;
         block_ = allocator_.allocate(T.sizeof);
         // SmartPtr logic snipped...
     }

     ~this()
     {
         // ...SmartPtr logic snipped
         allocator_.deallocate(block_);
     }

private:
     void[] block_;
     AllocatorInterface!AllocTraits allocator_;
}

alias DataPtr = SmartPtr!(Data, AllocTraits.nogc);

struct MyType
{
     this(DataPtr data) @nogc
     {
         data_ = move(data);
     }

private:
     DataPtr data_;
}

That *would* be able to rely on inference. Question is, what is 
AllocatorInterface? Should we push such type erasure to the users?


More information about the Digitalmars-d mailing list