Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec for ref local reassignment. #963

Merged
merged 3 commits into from
Oct 5, 2017
Merged

Spec for ref local reassignment. #963

merged 3 commits into from
Oct 5, 2017

Conversation

gafter
Copy link
Member

@gafter gafter commented Oct 3, 2017

Here is a proposed spec for ref local reassignment

/cc @dotnet/ldm @dotnet/roslyn-compiler @agocke

@benaadams
Copy link
Member

So you could reassign ref readonlys? (the left operand/holder variable rather than what it refers to)

Hadn't thought about that; but makes sense as is the chain ref->value that is readonly not the whole chain variable->ref->value

For writable refs I assume syntax is:

ref int currentRef = ref oldRef;
ref currentRef = ref newRef; // reassign

For readonly refs does the syntax include readonly?

As per declaration+init?

ref readonly int currentRef = ref readonly oldRef;
ref readonly currentRef = ref readonly newRef; // reassign

Or is readonly part of the type/declaration e.g. readonly int so its only ref

ref readonly int currentRef = ref readonly oldRef;
ref currentRef = ref readonly newRef; // reassign
ref currentRef = ref newRef; // auto-convert writable ref to readonly ref in reassign?

@jcouv
Copy link
Member

jcouv commented Oct 4, 2017

@benaadams Per OP the reassignment the syntax is currentRef = ref newRef; // reassign (no ref on the left). This will allow using it as an expression without redundant ref's.

@benaadams
Copy link
Member

The left side being ref is inferred from the right? e.g.

currentRef = newRef; // assign/copy value to pointed at ref
currentRef = ref newRef; // reassingn ref

That works

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
I'd suggest adding an example or two.

@jcouv
Copy link
Member

jcouv commented Oct 4, 2017

@benaadams It's not obvious, but I think the ref is properly seen as belonging with the =, not to the right-hand-side.


When the left operand binds to an `out` parameter, it is an error if that `out` parameter has not been definitely assigned at at the beginning of the ref assignment operator.

If the left operand is a writeable ref (i.e. it designates anything other than a `ref readonly` local or `in` parameter), then the right operand may not be a `ref readonly` local or parameter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d just say “must be a writeable lvalue”. That would cover fields (except readonly), ref/val locals (except readonly), ref ternaries (when both branches are writeable), etc...
We state same requirements when passing by ‘ref’.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

@@ -0,0 +1,27 @@
# Ref Local Reassignment

In C# 7.2, we add support for rebinding the referent of a ref local variable or a ref parameter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is nothing technically wrong with rebinding ref parameters. I guess - why not. Similarly as we allow assigning to byval params.

Copy link
Member

@benaadams benaadams Oct 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it cause confusion? I assume it would be detaching from the passed ref; but it may be assumed that its changing the passed ref (in the callee) rather than just the local parameter ref?

Unless it actually is ofc... :)

i.e.

I assume its changing a byval pointer (copy of pointer to reference) p* or &val the param itself
rather than a byref pointer (reference to pointer to reference) &p* or p** the callee's passed reference

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caller will see no effects from/after param rebinding by callee. But that could be the reason why callee does this...
It will not be the most common feature to use - like assignments to ordinary parameters, but nothing is wrong with it technically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm just over thinking it :)

People don't think changing a non-ref param changes it in callee and its not a ref ref or &ref type.

Also is probably handy so you have a starter ref that you then change rather than having to introduce a local copy that you then modify.

e.g.

ref T FindFrom(ref T current)
{
    ref var local = ref current;
    while (!local.IsCorrect)
    {
        local = ref _array[local.Next];
    }
    
    return ref local;
}

would be better as

ref T FindFrom(ref T current)
{
    while (!current.IsCorrect)
    {
        current = ref _array[current.Next];
    }
    
    return ref current;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though in practice it would be more like

static T NotFound;

T[] _array;

ref T FindFrom(ref T current, out bool success)
{
    var maxSteps = _array.Length;
    success = true;
    do
    {
        if (current.IsCorrect)
        {
            return ref current;
        }
        else if (current.Next < 0)
        {
            break;
        }
        current = ref _array[current.Next];
        maxSteps--;
    } while (maxSteps > 0);

    success = false;
    return ref NotFound;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also need to clarify position on rebinding of ‘this’ in struct methods. In theory all the arguments for allowing rebinding of ref parameters apply to ‘this’ in a struct.
So is the ‘this = ref something’ allowed in a struct method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as "this" in a class acts like a parameter, it cannot be assigned, "this" in a struct acts like a ref parameter, but cannot be rebound. I'll add that to the spec.


The safety rules for this operator are:

- For a ref reassignment `e1 = ref e2`, the *ref-safe-to-escape* of `e2` must be at least as wide a scope as the *ref-safe-to-escape* of `e1`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ref local declared with no initializer it has the “returnable” level, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly, we decided to defer supporting "ref local declared with no initializer" to some future revision.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would work too.
Although I think the need will arise quickly. Perhaps as soon as we start testing this :)

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
Some clarifying suggestions.

@gafter gafter merged commit ca982ef into dotnet:master Oct 5, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants