www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Interesting PRs: bringing type system legitimacy to shared allocators

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
https://github.com/dlang/phobos/pull/5355

Andrei
Apr 27
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Thursday, 27 April 2017 at 19:57:52 UTC, Andrei Alexandrescu 
wrote:
 https://github.com/dlang/phobos/pull/5355

 Andrei
And then we'd probably need INoGCAllocator and ISharedNOGCAllocator...
Apr 27
next sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Thursday, 27 April 2017 at 20:04:32 UTC, Stanislav Blinov 
wrote:
 On Thursday, 27 April 2017 at 19:57:52 UTC, Andrei Alexandrescu 
 wrote:
 https://github.com/dlang/phobos/pull/5355

 Andrei
And then we'd probably need INoGCAllocator and ISharedNOGCAllocator...
Wasn't one major selling point of compile time introspection / duck typing that we could stop using interfaces such... naming schemes?
Apr 27
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Thursday, 27 April 2017 at 23:12:48 UTC, Moritz Maxeiner wrote:
 On Thursday, 27 April 2017 at 20:04:32 UTC, Stanislav Blinov 
 wrote:
 On Thursday, 27 April 2017 at 19:57:52 UTC, Andrei 
 Alexandrescu wrote:
 https://github.com/dlang/phobos/pull/5355

 Andrei
And then we'd probably need INoGCAllocator and ISharedNOGCAllocator...
Wasn't one major selling point of compile time introspection / duck typing that we could stop using interfaces such... naming schemes?
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). 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.
Apr 27
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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. Allocators must, however, distinguish between reentrant and non-reentrant calls to the allocation primitives. With regard to whether the memory is collectable or not we need some more thinking.
 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. Thanks, Andrei
Apr 30
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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?
Apr 30
next sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 1 May 2017 at 00:43:22 UTC, Stanislav Blinov wrote:

         block_ = allocator_.allocate(T.sizeof);
Obviously, should be Block.sizeof, and
     AllocatorInterface!AllocTraits allocator_;
should be AllocatorInterface!traits allocator_
Apr 30
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/30/17 8:43 PM, Stanislav Blinov wrote:
 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?
The allocator itself may be shared across threads, in which case its primitives need to be reentrant. -- Andrei
Apr 30
parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 1 May 2017 at 04:54:28 UTC, Andrei Alexandrescu wrote:
 On 4/30/17 8:43 PM, Stanislav Blinov wrote:
 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?
The allocator itself may be shared across threads...
But it is no different in case of memory. Allocator that allocates shared memory should either return shared(void)[] or have an explicit interface (allocateShared et al.), not just have the "you may cast result of allocate() to shared" somewhere in the docs. We should let the language do the work it can, not send us to RTFM. Hence my earlier statement that more interfaces are needed, as one possible (albeit not pretty) solution.
May 01
prev sibling parent reply Guillaume Piolat <first.last gmail.com> writes:
On Sunday, 30 April 2017 at 21:43:26 UTC, Andrei Alexandrescu 
wrote:
 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.


 Thanks,

 Andrei
IAllocator being fully nogc would be a comforting guarantee, as runtime dispatch makes for lighter types.
May 01
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 05/01/2017 08:12 AM, Guillaume Piolat wrote:
 On Sunday, 30 April 2017 at 21:43:26 UTC, Andrei Alexandrescu wrote:
 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.


 Thanks,

 Andrei
IAllocator being fully nogc would be a comforting guarantee, as runtime dispatch makes for lighter types.
As I said (and am encouraging you to do this), this is achieved through simple variance: interface INoGCAllocator : IAllocator { ... override all methods here as nogc ... } This is possible because a nogc method may override one that is not nogc. nogc being more restrictive it is contravariant with no- nogc. Also IAllocator should have a few nogc methods to start with; there's no reason e.g. for empty() to create garbage. Could you please initiate a PR? Andrei
May 01
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 1 May 2017 at 13:00:27 UTC, Andrei Alexandrescu wrote:
 On 05/01/2017 08:12 AM, Guillaume Piolat wrote:
 On Sunday, 30 April 2017 at 21:43:26 UTC, Andrei Alexandrescu 
 wrote:
 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.


 Thanks,

 Andrei
IAllocator being fully nogc would be a comforting guarantee, as runtime dispatch makes for lighter types.
As I said (and am encouraging you to do this), this is achieved through simple variance: interface INoGCAllocator : IAllocator { ... override all methods here as nogc ... } This is possible because a nogc method may override one that is not nogc. nogc being more restrictive it is contravariant with no- nogc. Also IAllocator should have a few nogc methods to start with; there's no reason e.g. for empty() to create garbage. Could you please initiate a PR? Andrei
To follow this discussion up, and in light of yesterday's Collections presentation at DConf, I though I'd go ahead and make a basic implementation of traits-based IAllocator. You can find it here: https://github.com/radcapricorn/alloctraits It pretty much evades the shortcomings of IAllocator, alleviates the need for any additional interfaces (ISharedAllocator et al.), at the cost of making user code slightly more verbose. But that is a small price to pay if we want to benefit from the type system. The current implementation lacks a few features of std.experimental.allocator (i.e. passing implementation by pointer), which are easy to add, I was just focused mainly on roughing out the design as the first step. Anyone interested, feel free to comment, improve, bash, destroy...
May 07
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/7/17 1:30 PM, Stanislav Blinov wrote:

 
 To follow this discussion up, and in light of yesterday's Collections 
 presentation at DConf, I though I'd go ahead and make a basic 
 implementation of traits-based IAllocator.
 
 You can find it here:
 
 https://github.com/radcapricorn/alloctraits
Cool, thanks. Before we saw that, Eduard and I are working on a similar solution along the lines of IAllocator!(Flag!"nogc", Flag!"safe"). -- Andrei
May 07
parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Sunday, 7 May 2017 at 11:46:33 UTC, Andrei Alexandrescu wrote:
 On 5/7/17 1:30 PM, Stanislav Blinov wrote:

 
 To follow this discussion up, and in light of yesterday's 
 Collections presentation at DConf, I though I'd go ahead and 
 make a basic implementation of traits-based IAllocator.
 
 You can find it here:
 
 https://github.com/radcapricorn/alloctraits
Cool, thanks. Before we saw that, Eduard and I are working on a similar solution along the lines of IAllocator!(Flag!"nogc", Flag!"safe"). -- Andrei
I guess the exact representation of "traits" is something to decide on. I have to say, even though I used the BitFlags-based solution, I'm not a big fan of that, the syntax is a bit unwieldy. Then again, Flag-based would be too. Perhaps a struct with enum members could be used...
May 07
prev sibling next sibling parent reply "Nick Sabalausky (Abscissa)" <SeeWebsiteToContactMe semitwist.com> writes:
On 04/27/2017 07:12 PM, Moritz Maxeiner wrote:
 Wasn't one major selling point of compile time introspection / duck
 typing that we could stop using interfaces such... naming schemes?
Not that I speak for everyone, but the way I see it, no, the duck typing aspect is just something that's to be tolerated. The real gold is simply being freed from both the rigid mess and the runtime costs of class/interface hierarchies. I wish we DID have to include some kind of "implements ForwardRange" or "enum _thisStructImplements_ForwardRange" or some such to declare a type actually intends to be a ForwardRange (or whatever) and isn't merely qualifying as one by coincidence.
Apr 27
parent reply Atila Neves <atila.neves gmail.com> writes:
On Friday, 28 April 2017 at 01:12:39 UTC, Nick Sabalausky 
(Abscissa) wrote:
 On 04/27/2017 07:12 PM, Moritz Maxeiner wrote:
 Wasn't one major selling point of compile time introspection / 
 duck
 typing that we could stop using interfaces such... naming 
 schemes?
Not that I speak for everyone, but the way I see it, no, the duck typing aspect is just something that's to be tolerated. The real gold is simply being freed from both the rigid mess and the runtime costs of class/interface hierarchies. I wish we DID have to include some kind of "implements ForwardRange" or "enum _thisStructImplements_ForwardRange" or some such to declare a type actually intends to be a ForwardRange (or whatever) and isn't merely qualifying as one by coincidence.
https://github.com/atilaneves/concepts import concepts; models!(isForwardRange, MyType) struct MyType { .... } Atila
Apr 28
next sibling parent "Nick Sabalausky (Abscissa)" <SeeWebsiteToContactMe semitwist.com> writes:
On 04/28/2017 01:09 PM, Atila Neves wrote:
 https://github.com/atilaneves/concepts

 import concepts;

  models!(isForwardRange, MyType)
 struct MyType { .... }
Hmm, close, but if I'm reading the source right, it looks like a type doesn't have to use the UDA in order for isXXX or "static assert(models..." to be satisfied, which was the whole issue I had. (Also, it'd be nice if it didn't require both isXXX and checkXXX to be manually written, because given a checkXXX function, the isXXX function is just boilerplate.) Although I guess the checkXXX function could simply include a check the the UDA. Be nice if that was taken care of automatically though. Now if only we could get Phobos to do that... I do, however, like the improved diagnostics this offers, and the fact that the checkXXX function uses a much saner (simpler, easier to read/remember/understand) syntax than the usual `is(typeof((){ ... })))` mess. I may use this.
Apr 28
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Friday, 28 April 2017 at 17:09:22 UTC, Atila Neves wrote:
 https://github.com/atilaneves/concepts

 import concepts;

  models!(isForwardRange, MyType)
 struct MyType { .... }


 Atila
I remember you had posted about this last year. It looks like you've added some stuff on ranges to it recently...interesting. You might want to include an example in the Readme.md of using the concept as a template constraint for a function. Maybe like void callfoo(T)(T x) if (isFoo!T) { x.foo(); } Foo foo; callfoo(foo);
Apr 28
parent reply Atila Neves <atila.neves gmail.com> writes:
On Friday, 28 April 2017 at 18:04:06 UTC, jmh530 wrote:
 On Friday, 28 April 2017 at 17:09:22 UTC, Atila Neves wrote:
 https://github.com/atilaneves/concepts

 import concepts;

  models!(isForwardRange, MyType)
 struct MyType { .... }


 Atila
I remember you had posted about this last year. It looks like you've added some stuff on ranges to it recently...interesting. You might want to include an example in the Readme.md of using the concept as a template constraint for a function. Maybe like void callfoo(T)(T x) if (isFoo!T) { x.foo(); } Foo foo; callfoo(foo);
Done. I also added to the README that it has its own versions of the range constraints from Phobos that can be used with ` models`. Atila
Apr 28
parent reply Sebastiaan Koppe <mail skoppe.eu> writes:
On Friday, 28 April 2017 at 22:18:54 UTC, Atila Neves wrote:
 Done. I also added to the README that it has its own versions 
 of the range constraints from Phobos that can be used with 
 ` models`.

 Atila
Example of an error message in the README would be great too.
May 01
parent Atila Neves <atila.neves gmail.com> writes:
On Monday, 1 May 2017 at 07:42:19 UTC, Sebastiaan Koppe wrote:
 On Friday, 28 April 2017 at 22:18:54 UTC, Atila Neves wrote:
 Done. I also added to the README that it has its own versions 
 of the range constraints from Phobos that can be used with 
 ` models`.

 Atila
Example of an error message in the README would be great too.
Good point, done. Atila
May 02
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 04/27/2017 07:12 PM, Moritz Maxeiner wrote:
 On Thursday, 27 April 2017 at 20:04:32 UTC, Stanislav Blinov wrote:
 On Thursday, 27 April 2017 at 19:57:52 UTC, Andrei Alexandrescu wrote:
 https://github.com/dlang/phobos/pull/5355

 Andrei
And then we'd probably need INoGCAllocator and ISharedNOGCAllocator...
Wasn't one major selling point of compile time introspection / duck typing that we could stop using interfaces such... naming schemes?
The allocators design is interesting in that it has a full DbI core, on top of which resides a thin dynamically-type interface (IAllocator and ISharedAllocator). We're still exploring the idioms enabled by this interaction. -- Andrei
Apr 30
parent reply "Nick Sabalausky (Abscissa)" <SeeWebsiteToContactMe semitwist.com> writes:
On 04/30/2017 05:39 PM, Andrei Alexandrescu wrote:
 The allocators design is interesting in that it has a full DbI core, on
 top of which resides a thin dynamically-type interface (IAllocator and
 ISharedAllocator). We're still exploring the idioms enabled by this
 interaction. -- Andrei
I assume this dual-interface design (DbI + OO) is, much like std.digest, a way to have our template/DbI cake and still permit things to be selected at runtime too (via OO), right? If so, this is a topic that's been on my mind lately, and I'd like to offer some thoughts I've had (well, some partial thoughts anyway): Part A: While I like that the std.digest design allows both DbI and runtime-selection of arbitrary user-created types, it has a few issues: 1. It feels awkward that it needs essentially duplicate APIs under two different paradigms to pull it off. 2. If a library (such as mine) wants to support both versions, then it needs a little bit of extra plumbing to accommodate that. Or at least it seemed to in my case. (std.digest itself even has a little bit of extra plumbing because of it: WrapperDigest) 3. The OO version, being OO, does involve some type erasure. Now, fast-forward to: Part B: On a separate project, I've used Sonke's TaggedAlgebraic before <https://github.com/s-ludwig/taggedalgebraic>. It's kind of interesting: It's much like Phobos's Algebraic, except it allows function/member forwarding to the current underlying value *without* having to explicitly check what the current value's type is (Don't recall offhand exactly how it handles members that don't exist on the current type, probably throws I guess, although I'd imagine it would be possible to statically disallow access to members known at compile-time to not exist in *any* of the possible underlying types. But I digress). But then: Part C: At the time, I saw TaggedAlgebraic's member-forwarding as a minor little convenience feature. But the other day I was working with some code that used and built upon std.digest, and something occurred to me: If we had a type similar to TaggedAlgebraic, but was an open variant rather than a closed algebraic (Ie, a type like Phobos's Variant, but forwarded member access without first requiring explicit conversion to the exact underlying type), then *that* could be used to permit runtime-selection between arbitrary DbI types *without* the type-erasure and "duality" of adding in OO (or at least, less type-erasure anyway). Just maybe need the ability to filter the allowable types via a constraint (such as isBidirctionalRange, etc), and then, IIUC it should be possible for the variant-like supertype to qualify as an honest-to-goodness instance of the DbI interface. Voila: OO has been obsoleted. Destroy?
May 01
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 1 May 2017 at 16:31:10 UTC, Nick Sabalausky (Abscissa) 
wrote:

 If we had a type similar to TaggedAlgebraic...

 Destroy?
It's too strict: you have to specify concrete types beforehand. This spills over into user code and makes it far less versatile that can be achieved. Currently with allocators we have two extremes: IAllocator (and now, ISharedAllocator) that carries *no* static type information about concrete implementation, *and* loses all the inference regarding nogc and shared memory. On the other end it's CAllocatorImpl that carries *both*. But for user code, what is desirable is to have a middle man: a type that erases concrete allocator type but still keeps the nogc and/or shared. This would enable user types to exchange their allocators while keeping static checks afforded by the language. This can be done e.g. like this: enum AllocatorTraits { none = 0x00, noGC = 0x01, sharedMemory = 0x02, sharedInstance = 0x04, } mixin template AllocatorInterface() { Ternary owns(Block block) nogc; Ternary resolveInternalPointer(void* p, ref Block result) nogc; Ternary empty() const nogc; Block allocate(size_t, TypeInfo ti = null); Block alignedAllocate(size_t, uint); Block allocateAll(); bool expand(ref Block, size_t); bool reallocate(ref Block, size_t); bool alignedReallocate(ref Block, size_t, uint); bool deallocate(Block block); bool deallocateAll(); } template IAllocatorImpl(AllocatorTraits traits) { static if (traits & AllocatorTraits.sharedInstance) alias Base = ISharedAllocator; else alias Base = IAllocator; interface IAllocatorImpl : Base { static if (traits & AllocatorTraits.sharedMemory) alias Block = shared(void)[]; else alias Block = void[]; static if (traits & (AllocatorTraits.sharedInstance | AllocatorTraits.noGC)) nogc shared { mixin AllocatorInterface!(); } else static if (traits & AllocatorTraits.sharedInstance) shared { mixin AllocatorInterface!(); } else nogc { mixin AllocatorInterface!(); } mixin AllocatorInterface!(); } } ...and make the allocatorObject() function return IAllocatorImpl!traits instead of CAllocatorImpl!Allocator. With such IAllocatorImpl, user code can specify concrete expectations about the allocator without needing to know the exact allocator type.
May 01
next sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 2 May 2017 at 02:51:02 UTC, Stanislav Blinov wrote:

Lost one else. Should be

          static if (traits & (AllocatorTraits.sharedInstance |
  AllocatorTraits.noGC))
               nogc shared { mixin AllocatorInterface!(); }
          else static if (traits & AllocatorTraits.sharedInstance)
              shared { mixin AllocatorInterface!(); }
          else static if (traits & AllocatorTraits.noGC)
               nogc { mixin AllocatorInterface!(); }
          else
              mixin AllocatorInterface!();
May 01
prev sibling parent "Nick Sabalausky (Abscissa)" <SeeWebsiteToContactMe semitwist.com> writes:
On 05/01/2017 10:51 PM, Stanislav Blinov wrote:
 On Monday, 1 May 2017 at 16:31:10 UTC, Nick Sabalausky (Abscissa) wrote:

 If we had a type similar to TaggedAlgebraic...

 Destroy?
It's too strict: you have to specify concrete types beforehand.
No, that's why my suggestion was: "If we had a type similar to TaggedAlgebraic, but was an open variant rather than a closed algebraic (Ie, a type like Phobos's Variant, but forwarded member access without first requiring explicit conversion to the exact underlying type),"
May 01
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 04/27/2017 04:04 PM, Stanislav Blinov wrote:
 On Thursday, 27 April 2017 at 19:57:52 UTC, Andrei Alexandrescu wrote:
 https://github.com/dlang/phobos/pull/5355

 Andrei
And then we'd probably need INoGCAllocator and ISharedNOGCAllocator...
"shared" is a type qualifier essentially changing the type of the interface, so a separate interface is needed. For nogc, simple variance is sufficient: interface A { void fun(); } class B : A { void fun() nogc {} } That said, there are quite a few functions we should be able to qualify as nogc from the get-go, e.g. empty, expand, alignment etc. Would you like to try a PR to that effect? Thanks! Andrei
Apr 30