Thread overview
[Issue 22270] [DIP1000] class does infer scope in methods when assigned to a scope variable
Sep 03, 2021
João Lourenço
Sep 03, 2021
Dennis
Sep 03, 2021
João Lourenço
Sep 03, 2021
Dennis
Sep 03, 2021
João Lourenço
Sep 03, 2021
João Lourenço
Sep 03, 2021
Dennis
Sep 03, 2021
João Lourenço
Sep 03, 2021
Dennis
Sep 04, 2021
João Lourenço
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

João Lourenço <jlourenco5691@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |jlourenco5691@gmail.com

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

Dennis <dkorpel@live.nl> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|NEW                         |RESOLVED
                 CC|                            |dkorpel@live.nl
           Hardware|x86_64                      |All
         Resolution|---                         |INVALID
                 OS|Linux                       |All

--- Comment #1 from Dennis <dkorpel@live.nl> ---
Neither method gets `scope` inferred. If you remove the `new` in Bar (`scope bar = Bar;`) you'll get the same error for the struct. What's happening is that since `Bar` is a pointer, it gets dereferenced implicitly:

```
@safe
void main()
{
    scope Bar* bar = new Bar;
    (*bar).dummy; // dereference strips away the scope layer
}
```
`dummy` can't escape the pointer `bar` since `this` is passed by `ref`.
Escaping a pointer to a ref variable (`&this`) is fixed by
https://github.com/dlang/dmd/pull/12812.

The class case is not allowed since dummy can do this:

```
Foo globalFoo;
class Foo
{
    @safe void dummy() {
        globalFoo = this;
    }
}
```

So they are not consistent because `struct` is a value type and `class` a reference type.

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #2 from João Lourenço <jlourenco5691@gmail.com> ---
So I just tested this and it compiled fine with DIP1000
---
struct Bar
{
        @safe void dummy() {}
}

@safe
void main()
{
    scope bar = Bar();
    bar.dummy; // ok
}

---

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #3 from Dennis <dkorpel@live.nl> ---
(In reply to João Lourenço from comment #2)
> So I just tested this and it compiled fine with DIP1000

I forgot to mention, you also need to give `Bar` at least one pointer member.

```
struct Bar
{
    int* x;
    @safe void dummy() {}
}

@safe
void main()
{
    scope bar = Bar();
    bar.dummy; // ok
}
```

onlineapp.d(11): Error: scope variable `bar` assigned to non-scope parameter `this` calling onlineapp.Bar.dummy

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #4 from João Lourenço <jlourenco5691@gmail.com> ---
So, how can I achieve something like this while instantiating a class or struct with scope in @safe code?

---
struct Bar
{
    int[] somePointerToRestrictThis;

    @safe Wrapper dummy() return scope {
        Wrapper wrapper = { bar: &this };
        return wrapper;
    }
}

struct Wrapper
{
        Bar* bar;
}

@safe
void main()
{
    scope bar = Bar();
    cast(void) bar.dummy;
}
---

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #5 from João Lourenço <jlourenco5691@gmail.com> ---
Shouldn't scope with struct instances behave the same way as a normal instantiation though? The example above works if I don't instantiate with scope.

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #6 from Dennis <dkorpel@live.nl> ---
(In reply to João Lourenço from comment #4)
> So, how can I achieve something like this while instantiating a class or struct with scope in @safe code?

You can't do that with @safe and dip1000 currently, because scope is only one layer. Making bar scope means making the array `somePointerToRestrictThis` scope, so dummy creates a pointer to a scope array which can't be expressed. What you can do is encapsulate `bar` in Wrapper and only give `return scope` access, and mark dummy @trusted:

```
struct Bar
{
    int[] somePointerToRestrictThis;

    @trusted Wrapper dummy() return scope {
        Wrapper wrapper = { bar: &this };
        return wrapper;
    }
}

struct Wrapper
{
    private Bar* bar;

    // Must be explicitly `return scope` for our @trusted assumption
    @safe int[] access() return scope {
        return bar.somePointerToRestrictThis;
    }
}
```

But of course you're on your own ensuring there's no holes in your @trusted code.


> Shouldn't scope with struct instances behave the same way as a normal instantiation though? The example above works if I don't instantiate with scope.

Not entirely sure what you mean, but return scope means scope in, scope out. If the input is not scope (and thus is assumed to have infinite lifetime), then the output does not need to have lifetime restrictions either.

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #7 from João Lourenço <jlourenco5691@gmail.com> ---
I was referring to:

```
scope bar1 = Bar();
auto  bar2 = Bar();
```

I thought `scope` only meant instantiating on the stack when used this way. But I guess it also marks all fields as scope. If I understood correctly, the instance `bar1` lifetime is the current scope and so are all its members, and the instance `bar2` lifetime is the current scope but its members are assumed to have infinite lifetime. And that's why if instantiate Bar normally without scope I can do this in @safe code with dip1000:

```
struct Bar
{
    int[] arr;

    @safe Wrapper dummy() {
        Wrapper wrapper = { bar: &this };
        return wrapper;
    }
}

struct Wrapper { Bar* bar; }

@safe void main()
{
    auto bar = Bar();
    cast(void) bar.dummy;
}
```

--
September 03, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #8 from Dennis <dkorpel@live.nl> ---
(In reply to João Lourenço from comment #7)
> I was referring to:
> 
> ```
> scope bar1 = Bar();
> auto  bar2 = Bar();
> ```
> 
> I thought `scope` only meant instantiating on the stack when used this way. But I guess it also marks all fields as scope.

Scope *only* applies to the fields, there's nothing else it can apply to because a struct is a value type. A `struct S {int* x;}` is just an `int* x`. Both variables bar1 and bar2 are each just 16 bytes on the stack: the memory needed for the single int[] variable that is Bar's member (assuming a 64-bit target).

When you take the address of a local variable `&bar1` or `&bar2`, then the resulting pointer necessarily needs to be `scope` since it points to stack memory, but this is something the compiler does based on its knowledge of where variables are declared, this is *not* what `scope` does in `scope bar1 = Bar();`. That declares the `int[]` member scope, which is why you can't take the address of `bar1`, since then you need a scope pointer to a scope array.

> If I understood correctly,
> the instance `bar1` lifetime is the current scope and so are all its
> members, and the instance `bar2` lifetime is the current scope but its
> members are assumed to have infinite lifetime.

I think your confusion comes from the fact you think `scope` applies to the variable declaration, so you can have a `scope int` and then if you take the address of that you get a scope pointer. That's not an unreasonable thing to think, it's just not how it is designed in D. A `scope int` does nothing, a `scope int*` applies to the indirection.

--
September 04, 2021
https://issues.dlang.org/show_bug.cgi?id=22270

--- Comment #9 from João Lourenço <jlourenco5691@gmail.com> ---
Ah, got it! Thank you :)

--