Thread overview
Trying to understand DIP1000
May 05, 2017
Dukc
May 05, 2017
Dukc
May 05, 2017
Dukc
May 05, 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?
May 05, 2017
On Friday, 5 May 2017 at 18:04:32 UTC, Dukc wrote:
> 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 oh, the identity function won't fool the compiler either, it infers return scope, -dip25 or no.


May 05, 2017
On Friday, 5 May 2017 at 18:04:32 UTC, Dukc wrote:
> @safe void killDMan()
> {   auto outer = [RefCountedSlice!int(1)];
>     foreach(ref fail; outer)
>     {   outer = [RefCountedSlice!int(1)];
>         gcActivationAttempt = new int[30000];
>         fail[0] = 24;
>     }
> }

should be

int[] gcActivationAttempt;

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

but this compiles too