The code below compiles as expected with dip100. It's @safe because `range`'s return value has the lifetime extended from the `this` parameter: -------------------------------- struct Container { auto range() @safe return scope { static struct Range { Container *self; } return Range(&this); } } -------------------------------- Trying to escape the result of `range` then fails to compile, as expected. However, adding a slice member variable to the struct above causes this: ---------------------------------- struct Container { // as before int[] ints; ---------------------------------- $ dmd -preview=dip1000 bug2.d bug2.d(10): Error: cannot take address of scope parameter this in @safe function range From the original DIP, it's to be expected that `ints` would have the same lifetime as `this` and things should work. This is particularly bad because if the programmer attempts a workaround the code below compiles and shouldn't: https://github.com/atilaneves/automem/issues/26
This seems to come down to `ref` parameters. `this` is a `ref` parameter, right? This works: struct Container1 { int* ints; } Container1* range1(return scope Container1* that) @safe { return that; /* accepted */ } This doesn't: struct Container2 { int* ints; } Container2* range2(return scope ref Container2 that) @safe { return &that; /* Error: cannot take address of scope parameter that */ } As far as I see, the two should be same to DIP 1000.
I believe this is because adding a slice member to Container makes it a type with indirections, whereas it was not before. DIP1000 only applies to types with indirections. If you were to add an int member to Container instead, the code would still compile as adding an int member to Container does not make it a type with indirections.
> I believe this is because adding a slice member to Container makes it a type with indirections, whereas it was not before. DIP1000 only applies to types with indirections. In either case, `Range` is a type with an indirection, and `this` is an indirection itself, so the error message that "cannot take the address of a scope parameter this is @safe function range" makes no sense. The function is deliberately `return scope` so that the lifetime of the return value is tied to `this` and shouldn't be able to outlive it.
Let's do a little rewriting: ---- struct Range { Container *self; } struct Container { int* p; static Range range(return scope ref Container c) @safe { return Range(&c); } } ---- which produces the same error. More rewrites: ---- struct Range { Container *self; } struct Container { int* p; } Range range(return scope ref Container c) @safe { return Range(&c); } ---- produces the same error. More: ---- struct Container { int* p; } Container* range(return scope ref Container c) @safe { return &c; } ---- produces the same error. More: ---- int** range(return scope ref int* c) @safe { return &c; } ---- produces the same error: Error: cannot take address of ref parameter c in @safe function range Now, the return value is not `ref`, so the `return` applies to the `int*`, not the `ref`. But we're not returning the `int*`, we're returning the address of the `int*` and recall the `return` doesn't apply to that, hence the error.
The key to understanding perplexing examples is to ruthlessly rewrite them in terms of `int*` and plain functions. Get rid of slices, this references, structs, member functions, etc. which do nothing but obfuscate what is happening, as they are just fancier versions of int* and functions.