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?