Trying to understand DIP1000

Dukc via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Fri May 5 11:04:32 PDT 2017


Walters exciting yesterday talk made me think about dip1000 
again. I found out that I do not understand this:

Containers that own their data will be able to give access to 
elements by scope ref. The compiler ensures that the references 
returned never outlive the container. Therefore, the container 
can deallocate its payload (subject to control of multiple 
container copies, e.g. by means of reference counting). A basic 
outline of a reference counted slice is shown below:

@safe struct RefCountedSlice(T) {
	private T[] payload;
	private uint* count;

	this(size_t initialSize) {
		payload = new T[initialSize];
		count = new size_t;
		*count = 1;
	}

	this(this) {
		if (count) ++*count;
	}

	// Prevent reassignment as references to payload may still exist
	@system opAssign(RefCountedSlice rhs) {
		this.__dtor();
		payload = rhs.payload;
		count = rhs.count;
		++*count;
	}

     // Interesting fact #1: destructor can be @trusted
	@trusted ~this() {
		if (count && !--*count) {
			delete payload;
			delete count;
		}
	}

     // Interesting fact #2: references to internals can be given 
away
	scope ref T opIndex(size_t i) {
		return payload[i];
	}

	// ...
}

// Prevent premature destruction as references to payload may 
still exist
// (defined in object.d)
@system void destroy(T)(ref RefCountedSlice!T rcs);


I did some hacking and found out that -dip1000 flag and scope are 
not even needed for most cases:

@safe struct RefCountedSlice(T) {
     private T[] payload;
     private uint* count;

     this(size_t initialSize) {
         payload = new T[initialSize];
         count = new size_t;
         *count = 1;
     }

     this(this) {
         if (count) ++*count;
     }

     // Prevent reassignment as references to payload may still 
exist
     @system opAssign(RefCountedSlice rhs) {
         this.__dtor();
         payload = rhs.payload;
         count = rhs.count;
         ++*count;
     }

     @trusted ~this() {
         if (count && !--*count) {
             delete payload;
             delete count;
             import std.stdio;
         }
     }
     ref opIndex(size_t i) {
         return payload[i];
     }

     ref front(){return payload[0];}

     void popFront(){payload = payload[1 .. $];}

     auto empty(){return payload.length == 0;}
}

I tested that this does compile and work correctly:

@safe void main()
{   auto test = RefCountedSlice!string(16);

     int i = 0;
     foreach(ref e; test)
     {   foreach(unused; 0 .. i++) e ~= "s";
     }
     foreach(j; 2 .. 10)
     {   import std.stdio;
         test[j].writeln;
     }
}

The compiler is too cunning to let you to leak test[x] out of a 
function by reference, or take an address of it. Nor you can 
assing it to another variable, because that means copy semantics. 
And that all applies without using -dip1000 or even -dip25.

There's still one way I found to fool the compiler, at least 
without flags:

int[] gcActivationAttempt;

@safe void killDMan()
{   auto outer = [RefCountedSlice!int(1)];
     foreach(ref fail; outer)
     {   outer = [RefCountedSlice!int(1)];
         gcActivationAttempt = new int[30000];
         fail[0] = 24;
     }
}

Because that's the only hole I found that prevents generic @safe 
refcounted containers, I suppose the "scope ref" keyword somehow 
deals with that. But I cannot think of how.

Can somebody explain that?


More information about the Digitalmars-d-learn mailing list