D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 19752 - dip1000 isn't @safe if struct contains a slice
Summary: dip1000 isn't @safe if struct contains a slice
Status: RESOLVED INVALID
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: x86_64 Linux
: P1 critical
Assignee: No Owner
URL:
Keywords: safe
Depends on:
Blocks:
 
Reported: 2019-03-19 11:26 UTC by Atila Neves
Modified: 2020-03-04 08:37 UTC (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Atila Neves 2019-03-19 11:26:45 UTC
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
Comment 1 ag0aep6g 2019-03-19 18:38:42 UTC
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.
Comment 2 Meta 2019-03-19 20:52:26 UTC
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.
Comment 3 Atila Neves 2019-03-20 09:35:31 UTC
> 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.
Comment 4 Walter Bright 2020-03-04 08:35:04 UTC
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.
Comment 5 Walter Bright 2020-03-04 08:37:04 UTC
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.