Thanks to Walter Bright's recent comments at Dconf about memory safety, and my own lamentations about the continued use of C in all contexts where memory safety is crucial by overconfident programmers who believe they can do no wrong, I've decided to propose a baked-in RAII implementation for D. I would like to submit a DIP, but first I'd like to run it by the forum community and see what improvements need to be made, possibly due to my own naivety on the subject.

To qualify that, I am nowhere near an expert on memory management. But, I've spent enough time absorbing discussion on the topic through osmosis and reading on it due to the D community's concerns about it that I may as well make myself heard and be shot down if I'm wrong.

I understand if many people are resistant to building it into the language. Phobos already has it, and there's the automem library by Atila Neves. However, I think the perception shift gained by baking in these features will benefit D enormously. D users can run around all day trying to convince people that different library implementations of RAII exist so one need not use the GC, but the only thing that is going to convince the programming world at large is a giant announcement on Reddit, HackerNews, and other places that "Hey, D has RAII now!" This will also drive many new eyes to the language that never would have looked otherwise.

There are also the obvious syntactical benefits. Referencing an RAII object and its members would be literally no different than referencing a GC heap object. No need to fiddle with library constructs to extract one's reference.

Without further adieu, let's get started.

--- refcounted ---

Keyword "refcounted" allocates an object on the heap, and therefore uses an allocator. By default, a built-in malloc() implementation is used.

@nogc void main()
{
   auto refCountedObject = refcounted Object();
} // References go to 0, object is destroyed

The allocation method used by refcounted can be overloaded. The overload is a function which expects a type and a set of parameters that were passed to the constructor. One can allocate the object however they choose, call the passed constructor, and then the function expects a return of a reference to the allocated object.

Forgive my ignorance, I'm unsure how to handle a collection of parameters. Haven't had to do it yet.

ref T opRefCounted(T)(params)
{
   T* object = malloc(sizeof(T));
   object.this(params);
   return ref object;
}

opRefCounted() is ALWAYS UNSAFE SYSTEM CODE! This could manifest as a compiler warning whenever it is present that must be suppressed by a flag so the developer must acknowledge they have used a custom allocation scheme. There are, of course, other options for handling this, I'm just stating the most obvious.

--- unique ---

Keyword "unique" allocates an object on the stack. It is only accessible to the given scope, child scopes, or functions it is explicitly passed to. Therefore, it does not use an allocator.

@nogc void main()
{
   auto scopedObject = unique Object();
} // Fall out of scope, object destroyed

--- How new and GC fit in ---

Keyword "new", which allocates to the heap for the D garbage collector, may not be used with the @nogc attribute. Only refcounted and unique. No objects, functions, methods, or any other code within the scope of, or called from the scope of, a @nogc context, may allocate using new.

@nogc void main()
{
   auto refCountedObject = refcounted Object(); // Okay
   auto scopedObject = unique Object();         // Okay
   auto tracedObject = new Object();        // Error!

   {
       auto refCountedObject = refcounted Object(); // Okay
       auto scopedObject = unique Object();         // Okay
       auto tracedObject = new Object();        // Error!
   }
}

More examples using called functions.

void refCountedUsed()
{
   auto refCountedObject = refcounted Object();
}

void uniqueUsed()
{
   auto scopedObject = unique Object();
}

void newUsed()
{
   auto tracedObject = new Object();
}

@nogc void main()
{
   refCountedUsed(); // Okay
   uniqueUsed();     // Okay
   newUsed();        // Error!
}

void main()
{
   refCountedUsed(); // Okay
   uniqueUsed();     // Okay
   newUsed();        // Okay
}

All of these methods are legal when the GC is allowed.

void main()
{
   auto refCountedObject = refcounted Object(); // Okay
   auto scopedObject = unique Object();         // Okay
   auto tracedObject = new Object();        // Okay

   {
       auto refCountedObject = refcounted Object(); // Okay
       auto scopedObject = unique Object();         // Okay
       auto tracedObject = new Object();        // Okay
   }
}

I may be missing some things, but I've left out some exhaustive details since I'm sure many of you are already experienced in the subject and aren't looking for complete documentation in a proposal like this.

Feel free to level criticism, and let me know if I should submit a DIP.

Reply via email to