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