June 19, 2021

On Saturday, 19 June 2021 at 09:43:18 UTC, ag0aep6g wrote:

>

But:

  • That's still hard to figure out, especially with methods because ref this is invisible.

On top of that, scope could be invisible behind in.

Here's an idea: could return apply to both the ref and value if there is return scope?

scope no scope
ref return type / ref param both ref
value return type / ref param both ref
ref return type / value param value value
value return type / value param value value

The table would simply become:

scope no scope
ref param both ref
value param value value

You could even make this work then:

struct Vector {
    float[4] small; // return ref
    float[] large; // return scope
    bool isSmall;

    ref float opIndex(size_t i) return scope {
        return isSmall ? small[i] : large[i]; // dynamically choose
    }
}

I think this is sound because there is no way a ref outlives its scope members, so the lifetime of the returned value is simply the one of the ref (which is the smaller one). However, I feel like there's a caveat I overlooked. Can the solution really be this simple?

June 21, 2021

On Friday, 18 June 2021 at 15:44:02 UTC, Dennis wrote:

>

If you're still confused, I don't blame you

I didn't understand anything. This is so bad.

June 21, 2021

On Saturday, 19 June 2021 at 19:40:36 UTC, Dennis wrote:

>

I think this is sound because there is no way a ref outlives its scope members, so the lifetime of the returned value is simply the one of the ref (which is the smaller one). However, I feel like there's a caveat I overlooked. Can the solution really be this simple?

Well, I had to think this one really hard.

Your solution has one disadvantage:

struct CustomPtr {
   private ubyte* ptr;
   //works, but suboptimal
   ubyte* opUnary(string op)() if(op=="*") return scope {return ptr;}
}

With your solution, DIP1000 will prevent the return value of *customPtr outliving customPtr. We would ideally want only to prevent *customPtr outliving whatever customPtr.ptr points to, which is what happens now if I understood the table right.

But I can see no flaw in changing the field "ref arg / ref return / scope" to "both".

Whether the complexity of the current semantics are worth it because of the issue I mentioned, I'm not sure at all. It's highly likely there are more corner cases lurking about. I have a feeling that we should go with your proposal, or continue brainstorming. Deriving the scope semantics from return value makes too much assumptions about the intention.

June 21, 2021

On Monday, 21 June 2021 at 16:10:53 UTC, Dukc wrote:

>

With your solution, DIP1000 will prevent the return value of *customPtr outliving customPtr. We would ideally want only to prevent *customPtr outliving whatever customPtr.ptr points to, which is what happens now if I understood the table right.

That was my first reservation when considering it, but so far I failed to find a scenario where the scope outlives its ref. Take for example this:

@safe:
struct CustomPtr {
   private ubyte* ptr;
   ubyte* get() return scope {return ptr;}
}

void f(scope ubyte* ptr0) {
    scope ubyte* ptr1;
    ptr1 = ptr0; // fine
    {
        CustomPtr c;
        c.ptr = ptr0;
        ptr1 = c.get(); // scope variable `c` assigned to `ptr1` with longer lifetime
    }
}

While ptr0 can be assigned to ptr1, when going through a CustomPtr c, dmd still sees it as assigning "scope variable c".

June 21, 2021

On Monday, 21 June 2021 at 16:56:30 UTC, Dennis wrote:

>

While ptr0 can be assigned to ptr1, when going through a CustomPtr c, dmd still sees it as assigning "scope variable c".

I was about to say it might be that you need to write

void f(scope ubyte* ptr0) {
    scope ubyte* ptr1;
    ptr1 = ptr0; // fine
    {
        auto c = CustomPtr(ptr0);
        ptr1 = c.get();with longer lifetime
    }
}

...instead, but that fails too, at least on 2.096 release canditate.

As does this one:

ubyte* get(ref return scope ubyte* ptr){return ptr;}

void f(scope ubyte* ptr0) {
    scope ubyte* ptr1;
    ptr1 = ptr0; // fine
    {
        auto c = ptr0;
        ptr1 = c.get();
    }
}

I believe we can conclude that the field "ref argument / value return / scope" already reads either "ref" or "both", at least when you're returning a pointer. That being the case, your solution makes perfect sense.

July 05, 2021
On 6/18/2021 8:44 AM, Dennis wrote:
> Here's the reduced code:
> ```D
> struct Vector {
>      float[] _elements;
>      ref float opIndex(size_t i) scope return {
>          return this._elements[i];
>      }
> }
> ```

It *always* helps to reduce these examples to the bare minimum, i.e. strip all the higher level constructs out. The compiler absolutely must be consistent in this. The above reduces to:

  ref int test(ref scope return int* p)
  {
     return *p;
  }

Take a moment to satisfy yourself that it is logically the same. [] was replaced by *, float by int, i no longer needed, don't confuse things with operator overloading, replace `this` with a ref parameter making it a non-member function, and then strip away the struct wrapper.

This currently compiles with master and -dip1000 without error.

Examining Dennis' table, it has a [ref return type] and a [scope value], and so the `return` applies to the pointer type.

And so it is working as intended and is not a bug.

I totally understand that this is headache-inducing. The aspirin is realizing that 100% of these examples can be rewritten using *only* the following terms:

    int i;
    int* p;
    return i;
    return *p;
    return &i;
    return &p;

Write the example using these, sprinkling in ref, return, and scope.

If anything else appears, like class, struct, this, [], delegate, etc., more work is needed to reduce it.

Hope this helps!
July 05, 2021
On Monday, 5 July 2021 at 09:28:47 UTC, Walter Bright wrote:
>   ref int test(ref scope return int* p)
>   {
>      return *p;
>   }
[...]
> Examining Dennis' table, it has a [ref return type] and a [scope value], and so the `return` applies to the pointer type.

I think you're misreading something there.

For reference, the table:

> **Does the `return` attribute apply to the parameter's `ref` or the pointer value?**
> |                                  | `scope`   | no `scope` |
> |----------------------------------|-----------|------------|
> | `ref` return type / `ref` param  | **`ref`** | **`ref`**  |
> | value return type / `ref` param  | **value** | **`ref`**  |
> | `ref` return type / value param  | **value** | **value**  |
> | value return type / value param  | **value** | **value**  |

Your test function has a `ref` return type and a `ref` parameter, so we're looking at the first row. That row says `return` applies to the `ref` part of the parameter (with and without `scope`).


July 05, 2021
On 7/5/2021 3:01 AM, ag0aep6g wrote:
> On Monday, 5 July 2021 at 09:28:47 UTC, Walter Bright wrote:
>>   ref int test(ref scope return int* p)
>>   {
>>      return *p;
>>   }
> [...]
>> Examining Dennis' table, it has a [ref return type] and a [scope value], and so the `return` applies to the pointer type.
> 
> I think you're misreading something there.
> 
> For reference, the table:
> 
>> **Does the `return` attribute apply to the parameter's `ref` or the pointer value?**
>> |                                  | `scope`   | no `scope` |
>> |----------------------------------|-----------|------------|
>> | `ref` return type / `ref` param  | **`ref`** | **`ref`**  |
>> | value return type / `ref` param  | **value** | **`ref`**  |
>> | `ref` return type / value param  | **value** | **value**  |
>> | value return type / value param  | **value** | **value**  |
> 
> Your test function has a `ref` return type and a `ref` parameter, so we're looking at the first row. That row says `return` applies to the `ref` part of the parameter (with and without `scope`).


I think you might be right.
July 05, 2021
On Monday, 5 July 2021 at 10:44:54 UTC, Walter Bright wrote:
> On 7/5/2021 3:01 AM, ag0aep6g wrote:
>> On Monday, 5 July 2021 at 09:28:47 UTC, Walter Bright wrote:

>> Your test function has a `ref` return type and a `ref` parameter, so we're looking at the first row. That row says `return` applies to the `ref` part of the parameter (with and without `scope`).
>
>
> I think you might be right.

How the heck are us mere mortals supposed to use this stuff if you struggle to understand what it means?


July 05, 2021
On 7/5/2021 4:36 AM, claptrap wrote:
> How the heck are us mere mortals supposed to use this stuff if you struggle to understand what it means?

I make mistakes all the time. That's why D has unit tests built in, and an acceptance test suite.

One reason I like writing software is once I get it right, it stays right every time.

Actually, I find Dennis' table a tad confusing. An alternate formulation is:

---------
Consider the ambiguity of a `return ref scope` parameter. Which is it:

1. `return ref` and `scope`
2. `ref` and `return scope`

? The ambiguity is resolved by looking at the function return. If it returns
by `ref`, then it's (1), otherwise (2). This is evident in the table above.
This rule is arbitrary, precluding things like creating a `return ref` and
`return scope` for the same parameter.
----------