Jump to page: 1 26  
Page
Thread overview
DIP1000: The return of 'Extend Return Scope Semantics'
May 25, 2021
Dennis
May 25, 2021
kinke
May 25, 2021
Dennis
May 25, 2021
tsbockman
May 26, 2021
Atila Neves
May 26, 2021
jmh530
May 26, 2021
Paul Backus
May 26, 2021
jmh530
Jun 11, 2021
Walter Bright
May 26, 2021
Zach Attack!
May 27, 2021
tsbockman
Jun 11, 2021
Walter Bright
Jun 11, 2021
ag0aep6g
Jun 11, 2021
Walter Bright
Jun 11, 2021
Walter Bright
Jun 01, 2021
Atila Neves
May 27, 2021
vitoroak
May 29, 2021
Paulo Pinto
May 29, 2021
Paulo Pinto
Jun 11, 2021
Elronnd
May 25, 2021
Max Haughton
May 25, 2021
Dennis
May 25, 2021
Max Haughton
May 25, 2021
zjh
May 25, 2021
zjh
May 25, 2021
zjh
May 26, 2021
zjh
May 25, 2021
sighoya
May 26, 2021
Atila Neves
May 29, 2021
Dennis
May 30, 2021
ZachAttack!
Jun 11, 2021
Dukc
Jun 11, 2021
Walter Bright
Jun 11, 2021
Dennis
Jun 12, 2021
Walter Bright
Jun 15, 2021
Dennis
Jun 13, 2021
ZachAttack!
Jun 13, 2021
ZachAttack!
Jun 12, 2021
ag0aep6g
Jun 13, 2021
Walter Bright
Jun 13, 2021
ag0aep6g
Jun 12, 2021
Dennis
Jun 12, 2021
Dennis
Jun 12, 2021
Dennis
May 25, 2021

Background

The return attribute allows you to return a scope variable.
The compiler knows that the returned value has the same lifetime as the argument passed to the function.

// ┌───────────<──────────┐
int* identity(return int* x) @safe {
    return x;
}

void main() @safe {
    int x;
    //   ┌──────<──────┐
    int* y = identity(&x);
    static int* z; // global variable
    z = y;         // error! scope variable `y` assigned to non-scope `z`
}

What if you are not using a return value, but setting it to another parameter, e.g. using ref or out?

//                           ┌─────────<─────────┐
void assign(ref scope int* target, return int* source) @safe {
    target = source;
}

The compiler knows that source cannot be returned since it's a void function, so it makes the first parameter, but only the first parameter, the destination of the return parameter source.

void main() @safe {
    int x;
    int* y;
    //     ┌─<─┐
    assign(y, &x); // allowed
}

This feature has been proposed and implemented by Walter Bright: Issue 19097, fix Issue 19097 - Extend Return Scope Semantics.
It was merged on January 2019, but in August 2018 there was a discussion about it:
Re: Is @safe still a work-in-progress?

Mike Franklin:

>

Why not the first ref parameter regardless of whether it's the absolute first in the list. Why not the last ref parameter? Why not all ref parameters?

Walter Bright:

>

Good question. If this fairly restricted form solves the problems, then there is no need for the more flexible form. Things can always be made more flexible in the future, but tightening things can be pretty disruptive. Hence, unless there is an obvious and fairly strong case case for the flexibility, then it should be avoided for now.

So did the restricted form turn out to be good enough? Well, on March 23 2019, Build entire Phobos with -preview=dip1000 #6931 was merged, so it seems to be. Hurray!

Except... It turns out we're not quite done yet, because Druntime and Phobos still rely on a pretty major accepts-invalid bug to compile with -dip1000, see:
dip1000 + pure is a DEADLY COMBO

The good news is, Per Nordlöw and I have been working on fixing Phobos, and Druntime [1], [2], [3], [4] (also thanks to aG0aep6G, Walter Bright, Iain Buclaw, and reviewers). During this, we did stumble on cases where the restriction came up, such as BigUint.divMod having out parameters after the input parameters. More importantly:

The issue

It turns out that core.lifetime: move (which is also in Phobos), has a signature that's incompatible with -dip1000 in its current form:

void move(T)(ref T source, ref T target)

We want to express:

//                       ┌─────────>─────────┐
void move(T)(return T source, ref scope T target)

But there's currently now way of making target the return scope destination when it's not the first parameter. So unless we want to leave move @system or create a move2 function with parameters reversed, we're back at the drawing board for "Extend Return Scope Parameters". Two earlier proposals were:

// Mike Franklin's proposal:
void move(T)(return(target) T source, ref scope T target)

// Steven Schveighoffer's proposal:
void move(T)(return T source, @__sink ref scope T target)

What do you think?

May 25, 2021

On Tuesday, 25 May 2021 at 11:24:59 UTC, Dennis wrote:

>

But there's currently now way of making target the return scope destination when it's not the first parameter. So unless we want to leave move @system or create a move2 function with parameters reversed, we're back at the drawing board for "Extend Return Scope Parameters". Two earlier proposals were:

// Mike Franklin's proposal:
void move(T)(return(target) T source, ref scope T target)

// Steven Schveighoffer's proposal:
void move(T)(return T source, @__sink ref scope T target)

What do you think?

Good overview, thanks for the post. If we don't really need a generic solution for that, i.e., can expect new code to use the first param and move to be the only problematic existing code, a possibility would be to make move and forward compiler intrinsics. The compiler can handle those much more efficiently than a non-trivial library solution with according template bloat.

May 25, 2021

On Tuesday, 25 May 2021 at 11:24:59 UTC, Dennis wrote:

>

Background

The return attribute allows you to return a scope variable.
The compiler knows that the returned value has the same lifetime as the argument passed to the function.

// ┌───────────<──────────┐
int* identity(return int* x) @safe {
    return x;
}

[...]

I think the Franklin solution is the best one presented here. Similarly with inout, any keywords in this context (i.e. non-trivial and non-local effect on the code) should aim for some syntax like this, even if the existing rules technically mean you could go without, flexibility and redundancy are a good thing (especially for people new to the language).

May 25, 2021

On Tuesday, 25 May 2021 at 11:50:29 UTC, Max Haughton wrote:

>

I think the Franklin solution is the best one presented here.

It looks nice, but it's also the one with the most implementation work. It requires a change to the parser (+libdparse), new mangling rules, and if we want to support it: it opens the door for multiple return parameters with different destinations.

Looking at the implementation dmd.escape(603), it's not going to be me who will be able to implement that any time soon.

May 25, 2021

On Tuesday, 25 May 2021 at 12:48:55 UTC, Dennis wrote:

>

On Tuesday, 25 May 2021 at 11:50:29 UTC, Max Haughton wrote:

>

I think the Franklin solution is the best one presented here.

It looks nice, but it's also the one with the most implementation work. It requires a change to the parser (+libdparse), new mangling rules, and if we want to support it: it opens the door for multiple return parameters with different destinations.

Looking at the implementation dmd.escape(603), it's not going to be me who will be able to implement that any time soon.

This is unfortunately true.

That being said I think "too hard to implement" is being said a lot these days, and it has nothing to do with documentation (in light of the other thread).

May 25, 2021

The meaning of function has limited the position of return,
Sovoid move(T)(return(target) T source, ref scope T target)is enough.
return(target)can be added to any parameter.

May 25, 2021

The meaning of function has limited the position of return,
Sovoid move(T)(return(target) T source, ref scope T target)is enough.
return(target)can be added to any parameter.

May 25, 2021

On Tuesday, 25 May 2021 at 11:46:12 UTC, kinke wrote:

>

Good overview, thanks for the post. If we don't really need a generic solution for that, i.e., can expect new code to use the first param and move to be the only problematic existing code, a possibility would be to make move and forward compiler intrinsics. The compiler can handle those much more efficiently than a non-trivial library solution with according template bloat.

Even without moving the implementation inside the compiler, an easy fix would be to make the druntime function a special case in dmd. I recently found out Walter did something similar to make char[].dup implicitly convert to immutable(char)[]. It's ugly, but it does move -dip1000 forward.

May 25, 2021

We can limit each parameter to one return at most.
It's not a bad thing to have multiple return areas. My C++ function f(A,B,C,D,...),every even parameter` can be used as the return value.

May 25, 2021
On 5/25/21 7:24 AM, Dennis wrote:
> But there's currently now way of making `target` the return scope destination when it's not the first parameter. So unless we want to leave `move` `@system` or create a `move2` function with parameters reversed, we're back at the drawing board for "Extend Return Scope Parameters". Two earlier proposals were:
> 
> ```D
> // Mike Franklin's proposal:
> void move(T)(return(target) T source, ref scope T target)
> 
> // Steven Schveighoffer's proposal:
> void move(T)(return T source, @__sink ref scope T target)
> ```
> 
> What do you think?

Re-reading that thread, there is more to the proposal than just the attribute -- the fact that the @__sink attribute would be inferred on templates. So no attribute would be necessary on `move`, even if the attribute is added for non-templates/non-auto functions (ditto for Mike's proposal).

That being said, Mike's proposal is obviously more flexible, as it allows multiple return parameters that link to multiple input parameters. But the cases in which those are present are super-rare.

I also don't like having a keyword (return) match with an attribute (@__something), as they seem unrelated, and the only reason an attribute would be to avoid adding a keyword. But that's a bit of bikeshedding (we could actually add a __keyword anyway since double-underscores are reserved).

-Steve
« First   ‹ Prev
1 2 3 4 5 6