August 23, 2018
On 8/23/18 9:32 AM, Steven Schveighoffer wrote:
> On 8/23/18 4:58 AM, Walter Bright wrote:
>> On 8/22/2018 6:50 AM, Steven Schveighoffer wrote:
>>> As for things being made "more flexible in the future" this basically translates to code breakage. For example, if you are depending on only the first parameter being considered the "return" value, and all of a sudden it changes to encompass all your parameters, your existing code may fail to compile, even if it's correctly safe and properly annotated.
>>
>> It's a good point. But I don't see an obvious use case for considering all the ref parameters as being returns.
> 
> You would have to consider the shortest liftetime and assume everything goes there. It would restrict your legitimate calls. Only mitigating factor may be if you take the ones you aren't going to modify as const or inout.

Actually, thinking about this, the shortest lifetime is dictated by how it is called, so there is no valid way to determine which one makes sense when compiling the function.

In order for this to work, you'd have to attribute it somehow. I can see that is likely going to be way more cumbersome than it's worth.

If I had to design a specific way to allow the common case to be easy, but still provide a mechanism for the uncommon cases, I would say:

1. define a compiler-recognized attribute (e.g. @__sink).
2. If @__sink is applied to any parameter, that is effectively the return value.
3. In the absence of a @__sink designation on non-void-returning functions, it applies to the return value.
4. In the absence of a @__sink designation on void returning functions, it applies to the first parameter.
5. Inference of @__sink happens even on non-templates.
6. If @__sink is attributed on multiple parameters, you assume all return parameters are assigned to all @__sink parameters for the purposes of verifying lifetimes are not exceeded.

Ugly to specify, but might actually be pretty non-intrusive to use.

-Steve
August 23, 2018
On Thursday, 23 August 2018 at 15:14:07 UTC, Steven Schveighoffer wrote:
> On 8/23/18 9:32 AM, Steven Schveighoffer wrote:
>> [...]
>
> Actually, thinking about this, the shortest lifetime is dictated by how it is called, so there is no valid way to determine which one makes sense when compiling the function.
>
> In order for this to work, you'd have to attribute it somehow. I can see that is likely going to be way more cumbersome than it's worth.
>
> If I had to design a specific way to allow the common case to be easy, but still provide a mechanism for the uncommon cases, I would say:
>
> 1. define a compiler-recognized attribute (e.g. @__sink).
> 2. If @__sink is applied to any parameter, that is effectively the return value.
> 3. In the absence of a @__sink designation on non-void-returning functions, it applies to the return value.
> 4. In the absence of a @__sink designation on void returning functions, it applies to the first parameter.
> 5. Inference of @__sink happens even on non-templates.
> 6. If @__sink is attributed on multiple parameters, you assume all return parameters are assigned to all @__sink parameters for the purposes of verifying lifetimes are not exceeded.
>
> Ugly to specify, but might actually be pretty non-intrusive to use.
>
> -Steve

This is more a general reply to the thread.

If I think I'm getting a good grasp on the issue here, it seems like something Rust already solved with lifetime annotations. Could they or something similar be leveraged for D as well?

https://doc.rust-lang.org/1.9.0/book/lifetimes.html

Current solution just seems too specific and very restrictive.
August 23, 2018
On Thursday, 23 August 2018 at 15:48:00 UTC, Chris M. wrote:
> On Thursday, 23 August 2018 at 15:14:07 UTC, Steven Schveighoffer wrote:
>> On 8/23/18 9:32 AM, Steven Schveighoffer wrote:
>>> [...]
>>
>> Actually, thinking about this, the shortest lifetime is dictated by how it is called, so there is no valid way to determine which one makes sense when compiling the function.
>>
>> In order for this to work, you'd have to attribute it somehow. I can see that is likely going to be way more cumbersome than it's worth.
>>
>> If I had to design a specific way to allow the common case to be easy, but still provide a mechanism for the uncommon cases, I would say:
>>
>> 1. define a compiler-recognized attribute (e.g. @__sink).
>> 2. If @__sink is applied to any parameter, that is effectively the return value.
>> 3. In the absence of a @__sink designation on non-void-returning functions, it applies to the return value.
>> 4. In the absence of a @__sink designation on void returning functions, it applies to the first parameter.
>> 5. Inference of @__sink happens even on non-templates.
>> 6. If @__sink is attributed on multiple parameters, you assume all return parameters are assigned to all @__sink parameters for the purposes of verifying lifetimes are not exceeded.
>>
>> Ugly to specify, but might actually be pretty non-intrusive to use.
>>
>> -Steve
>
> This is more a general reply to the thread.
>
> If I think I'm getting a good grasp on the issue here, it seems like something Rust already solved with lifetime annotations. Could they or something similar be leveraged for D as well?
>
> https://doc.rust-lang.org/1.9.0/book/lifetimes.html
>
> Current solution just seems too specific and very restrictive.

Heck, now that I'm looking at it, DIP25 seems like a more restricted form of Rust's lifetimes. Let me know if I'm just completely wrong about this, but

@safe ref int identity(return ref int x) {
    return x; // fine
}

would basically be like (pseudosyntax)

@safe ref'a int identity(ref'a int x) {
    return x; // fine
}

Maybe the more sane thing would be a syntax that visually ties them together as above. Obviously we're looking at possibly breaking changes, but how widespread would they be?

void betty(ref'a scope int* r, scope'a int* p); // syntax is not so nice since I just arbitrarily stuck them on different keywords, but that's besides the point here
August 23, 2018
On Thursday, 23 August 2018 at 23:36:07 UTC, Chris M. wrote:
> 
> Heck, now that I'm looking at it, DIP25 seems like a more restricted form of Rust's lifetimes. Let me know if I'm just completely wrong about this, but
> [snip]

Check out DIP1000

https://github.com/dlang/DIPs/blob/master/DIPs/DIP1000.md
August 24, 2018
On Thursday, 23 August 2018 at 23:58:00 UTC, jmh530 wrote:
> On Thursday, 23 August 2018 at 23:36:07 UTC, Chris M. wrote:
>> 
>> Heck, now that I'm looking at it, DIP25 seems like a more restricted form of Rust's lifetimes. Let me know if I'm just completely wrong about this, but
>> [snip]
>
> Check out DIP1000
>
> https://github.com/dlang/DIPs/blob/master/DIPs/DIP1000.md

I've read through it already, I'm just throwing out an idea for what I think is a more flexible solution to Walter's current issue which seems to be stemming more from DIP25 rather than DIP1000. More effort but I think it'd be worth it in the long run.
August 24, 2018
On Thursday, 23 August 2018 at 23:36:07 UTC, Chris M. wrote:

> Heck, now that I'm looking at it, DIP25 seems like a more restricted form of Rust's lifetimes. Let me know if I'm just completely wrong about this, but

I think DIP 25 is analogous to Problem #3 for Rust's Non-Lexical Lifetimes:

http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/#problem-case-3-conditional-control-flow-across-functions

http://smallcultfollowing.com/babysteps/blog/2016/05/09/non-lexical-lifetimes-adding-the-outlives-relation/#problem-case-3-revisited

> would basically be like (pseudosyntax)
>
> @safe ref'a int identity(ref'a int x) {
>     return x; // fine
> }
>
> Maybe the more sane thing would be a syntax that visually ties them together as above. Obviously we're looking at possibly breaking changes, but how widespread would they be?
>
> void betty(ref'a scope int* r, scope'a int* p); // syntax is not so nice since I just arbitrarily stuck them on different keywords, but that's besides the point here

I wish I had been more involved in D when DIP 25 and DIP 1000 were being proposed, as I don't think the designs were thoroughly vetted.  It's taken me at least a year to even begin getting a grasp on it.

I think DIP 25 and DIP 1000 should have been combined and thought of holistically as simply "annotated lifetimes in D" rather than separate things.  I think then it becomes easier to visualize what the problem is and see, potentially many, alternatives.

Given the investments that have already been made in DIP 25 and DIP 1000, it's going to take an extremely motivated individual to fight an uphill battle to change direction now, I'm afraid.  If working on D was my full-time job, I'd do it, but who in this community has such resources.

Mike


August 24, 2018
On Friday, 24 August 2018 at 00:13:48 UTC, Mike Franklin wrote:
> On Thursday, 23 August 2018 at 23:36:07 UTC, Chris M. wrote:
>
>> Heck, now that I'm looking at it, DIP25 seems like a more restricted form of Rust's lifetimes. Let me know if I'm just completely wrong about this, but
>
> I think DIP 25 is analogous to Problem #3 for Rust's Non-Lexical Lifetimes:
>
> http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/#problem-case-3-conditional-control-flow-across-functions
>
> http://smallcultfollowing.com/babysteps/blog/2016/05/09/non-lexical-lifetimes-adding-the-outlives-relation/#problem-case-3-revisited
>

Seems to be more of a warning of what issues we may face if DIP25/DIP1000 are finally implemented. It would be good to consider NLLs as well before D is committed. No point in repeating issues that have already been studied.

>> would basically be like (pseudosyntax)
>>
>> @safe ref'a int identity(ref'a int x) {
>>     return x; // fine
>> }
>>
>> Maybe the more sane thing would be a syntax that visually ties them together as above. Obviously we're looking at possibly breaking changes, but how widespread would they be?
>>
>> void betty(ref'a scope int* r, scope'a int* p); // syntax is not so nice since I just arbitrarily stuck them on different keywords, but that's besides the point here
>
> I wish I had been more involved in D when DIP 25 and DIP 1000 were being proposed, as I don't think the designs were thoroughly vetted.  It's taken me at least a year to even begin getting a grasp on it.
>
> I think DIP 25 and DIP 1000 should have been combined and thought of holistically as simply "annotated lifetimes in D" rather than separate things.  I think then it becomes easier to visualize what the problem is and see, potentially many, alternatives.
>
> Given the investments that have already been made in DIP 25 and DIP 1000, it's going to take an extremely motivated individual to fight an uphill battle to change direction now, I'm afraid.  If working on D was my full-time job, I'd do it, but who in this community has such resources.

I think the main problem is that these were created before the DIP process was fully fleshed out, so who knows how much vetting they got. At least they are still compiler switches and probably not widely used, so it's not like we're already too far gone. I do also agree they should be worked on in conjunction, the hard part is finding someone to take ownership and put in the extra effort.

>
> Mike


August 24, 2018
On 8/23/2018 6:32 AM, Steven Schveighoffer wrote:
> Furthermore any member function (or UFCS function for that matter) REQUIRES the first parameter to be the aggregate. How do you make a member function that stuffs the return into a different parameter properly typecheck?

What I propose is that the function interface be refactored so it does fit into these patterns. Is that an unreasonable requirement? I don't know. But it doesn't seem to be, as I haven't run into it yet.


>> Phobos doesn't do this by accident. It's how constructors work (see above) and how pipeline programming works.
> 
> Constructors I agree are reasonable to consider `this` to be the return value. On that point, I would say we should definitely go ahead with making that rule, and I think it will lead to no confusion whatsoever.
> 
> pipeline programming depends on returning something other than `void`, so I don't see how this applies.

grep Phobos for instances of put() and see its signature. It's part of pipeline programming, and it's all over the place.


> You would have to consider the shortest liftetime and assume everything goes there.

That's right.


> It would restrict your legitimate calls.

Maybe that's a good thing. Having multiple simultaneous routes of data out of a function is not good practice (note that it is impossible with functional programming). If you absolutely must have it, the exit routes can be aggregated into a struct, then pass that struct as the first argument.


> I want to stress that it may be a valid solution, but we should strive to prove the solutions are the best possible rather than just use duct-tape methodology.

I don't know how to prove anything with programming languages.


> It should even be considered that perhaps there are better solutions even than the approach dip1000 has taken.

People have hypothesized that for several years, and so far none have been forthcoming beyond a few hand-wavy generalities.


> I also want to point out that the attitude of 'we could just fix it, but nobody will pull my request' is unhelpful. We want to make sure we have the best solution possible, don't take criticism as meaningless petty bickering. People are genuinely trying to make sure D is improved. Hostility towards reviews or debate doesn't foster that.

I'm not hostile to debate. I just don't care for "this is uncharted territory, so let's do nothing" which has been going on for probably 4 years now, coincident with "scope is incomplete, D sux".

I.e. lead, follow, or get out of the way :-)
August 24, 2018
On 8/23/2018 8:14 AM, Steven Schveighoffer wrote:
> If I had to design a specific way to allow the common case to be easy, but still provide a mechanism for the uncommon cases, I would say:
> 
> 1. define a compiler-recognized attribute (e.g. @__sink).
> 2. If @__sink is applied to any parameter, that is effectively the return value.
> 3. In the absence of a @__sink designation on non-void-returning functions, it applies to the return value.
> 4. In the absence of a @__sink designation on void returning functions, it applies to the first parameter.
> 5. Inference of @__sink happens even on non-templates.
> 6. If @__sink is attributed on multiple parameters, you assume all return parameters are assigned to all @__sink parameters for the purposes of verifying lifetimes are not exceeded.


'ref' is already @__sink.
August 24, 2018
On 8/23/2018 5:58 PM, Chris M. wrote:
> Seems to be more of a warning of what issues we may face if DIP25/DIP1000 are finally implemented. It would be good to consider NLLs as well before D is committed. No point in repeating issues that have already been studied.

DIP25 waqs "finally implemented" several years ago, and works well. DIP1000 was implemented as well, it works, but it didn't cover the case of returning through a ref parameter.

There's no way to "thoroughly vet" them before implementing. It doesn't happen with C++, either, somebody builds an implementation and then people try it out.