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

Proposal: out var declaration #6183

Closed
gafter opened this issue Oct 20, 2015 · 53 comments
Closed

Proposal: out var declaration #6183

gafter opened this issue Oct 20, 2015 · 53 comments

Comments

@gafter
Copy link
Member

gafter commented Oct 20, 2015

This feature would allow a variable be declared at the same time that is it passed as an out parameter:

argument-value:
    out type identifier

A variable declared this way is read-only and scoped to the enclosing statement. More specifically, the scope will be the same as for a pattern-variable introduced via pattern-matching.

You should be able to use the contextual keyword var for the variable's type, in which case the specification for overload resolution would have to be modified to account for that possibility.

@bbarry
Copy link

bbarry commented Oct 20, 2015

To clarify:

A variable declared this way is read-only and scoped to the enclosing statement.

if (int.TryParse(input, out var value)) 
{
    // value here - readonly
}
else
{
    // value not in scope
}
// value not in scope

...

bool test = int.TryParse(input, out var value);
// value not in scope

@orthoxerox
Copy link
Contributor

Do we really need this with struct tuples and nullables? Why not enhance the stdlib with int? Int.TryParse(string input) and (int quotient, int remainder) Math.DivRem(int dividend, int divisor) instead?

@FunctionalFirst
Copy link

What about using tuples to capture out parameters as in F#?

let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")

@vladd
Copy link

vladd commented Oct 20, 2015

@gafter Do I understand the use case right:

var r = int.tryParse(str, out var n);

makes r non-readonly and n readonly? If so, both of them are "just function output arguments", why the difference?

@alrz
Copy link
Contributor

alrz commented Oct 20, 2015

@FunctionalFirst That would totally change method invocation semantics for any value. However, with #6067 you can deconstruct the out variable right away.

@FunctionalFirst
Copy link

@alrz Only for methods with out parameters, and it's a good change as it improves the locality of return values. You can also deconstruct the tuple right away with pattern matching.

@gafter
Copy link
Member Author

gafter commented Oct 20, 2015

@bbarry Not quite. The variable will be scoped to the "then" clause only (not the "else" clause) because that's the way pattern variables work. But correct: "out variables" in a local variable initializer do not escape to the enclosing scope. If you want it in the enclosing scope you know where to declare it.

@orthoxerox We do no intend to revisit every existing API that uses out parameters, change the recommendation for designing APIs, nor deprecate out parameters

@vladd You did not declare r as having any special relation to a method invocation. It is an ordinary local variable placed into the enclosing scope. n, on the other hand, is defined by reference to the method invocation (the grammar ties it to argument-value) and is not placed into the enclosing scope. If you want it mutable or in the enclosing scope, you know where to declare it.

@HaloFour
Copy link

@gafter Do the same scoping rules apply to conditional operations? Probably wouldn't matter much unless another out declaration was used in the false expression.

@gafter
Copy link
Member Author

gafter commented Oct 21, 2015

@HaloFour If you declare a pattern variable or an out variable in the condition of a ternary expression, it will be in scope throughout the enclosing statement (unless it is part of the condition of an if statement, in which case it won't be available in the else clause). However, in the case of a pattern variable, it is likely not to be definitely assigned within the third clause of the ternary expression:

string s = "123";
var result = int.TryParse(s, out var value)
    ? value // OK, in scope
    : value; // OK, but according to the spec for TryParse, always zero here.

compare this to

object o = 123;
var result = o is int value
    ? value // OK, in scope
    : value; // error: in scope, but not definitely assigned

@paulomorgado
Copy link

Aren't the "then" and the else two branches of the same if statement?

So, this:

var result = ThisOrThat("123", out var value)
    ? value
    : value;

will not be the same as:

int result;
if (ThisOrThat("123", out var value))
{
    result = value;
}
else
{
    result = value;
}

I'm not worried about all the refactorings out there because this will be a new case. But it doesn't make sense to me.

@gafter
Copy link
Member Author

gafter commented Oct 21, 2015

@paulomorgado Yes and yes.

The reason for this rule is that we expect people to often write code like

if (o is FooNode node && node.Whatever) { ... code using node ... } else
if (Something && o is BarNode node) { ... code using node ... } else
if (o is BazNode node) { ... code using node ... }

and we don't want to force them to have to think up new names for every node. This trick doesn't work in the ternary expression because the scope is expanded to the enclosing statement.

Also we don't want different but overlapping scoping rules for pattern variables and these "out" variables.

And anyway Swift does it this way (for pattern variables) so it must be right 😉

@paulomorgado
Copy link

@gafter, isn't that use case covered by pattern matching constructs like match and switch?

@gafter
Copy link
Member Author

gafter commented Oct 21, 2015

@paulomorgado No, not always, including in this example where the second if condition starts with a boolean expression.

@orthoxerox
Copy link
Contributor

@gafter: We do no intend to revisit every existing API that uses out parameters, change the recommendation for designing APIs, nor deprecate out parameters

Why not? Why implement better syntactic constructs and then keep using the old ones? out parameters are worse than proper return values from all standpoints. I'm not talking about removing the old APIs or even deprecating them in the next version, but about providing a more fluent alternative.

@gafter
Copy link
Member Author

gafter commented Oct 21, 2015

@orthoxerox Because there is lots of code using the recommended style, and we'd prefer not to jerk our customers around from one fad to another. I imagine some API authors may want to provide alternatives, but I also expect that we may elect to do as F# does and treat out parameters as tuple results, in which case API authors won't have anything to change.

@vladd
Copy link

vladd commented Oct 21, 2015

@gafter Point about scope taken, but again, why the immutability? Which advantage does it bring? This and let-expressions in LINQ seem to be the only places where readonlyness is implicit.

@gafter
Copy link
Member Author

gafter commented Oct 21, 2015

@vladd This would be consistent with all range variables in LINQ, and all pattern variables. Given the limited scope, there isn't a lot of value in being able to mutate them. This is also consistent with a theme in a bunch of proposed features to make it easier to do things that are safe, rather than making the unsafe things easier to do than the safe alternatives.

@paulomorgado
Copy link

@gafter,

@paulomorgado No, not always, including in this example where the second if condition starts with a boolean expression.

Oops! Missed that when I wrote it.

I think we have two distinct cases here.

On one hand, we have a condition where a variable is definitely assigned after its evaluation:

(ThisOrThat("123", out var value))

And the other hand a condition where a variable is only definitely assign if the condition is true:

(o is BazNode node)

Besides the fact that this difference might be hard to specify and/or implement, wouldn't you agree with me that the variable is expected to be in scope of the else in the first case but not in the second case?

You might even argue that this:

(o is Something(out a, out b))

although having an implementation similar to the first case, falls semantically on the second case.

The bottom like is that I feel the, in the first case, the scope of value should be the if statement. And I don't see any restrictions or caveats of being that way that are not present today.

On the other hand, I think that any variable declared on the scope of a pattern matching construct (switch or match) should be scoped to that branch.

@gafter
Copy link
Member Author

gafter commented Oct 22, 2015

@paulomorgado We prefer not to have separate scoping rules for the two cases. If it comes to that we'd probably prefer to drop this feature.

@leppie
Copy link
Contributor

leppie commented Oct 22, 2015

Something seems missing here with regards to scoping.

Also the continuous mention of if/then/else is just confusing as every example assumes some a boolean return value.

What about all the other cases?

Examples:

DivMod(num, den, out var div, out var mod); 

Foo(Bar(out var baz), baz); 

I can't see how any of this would work if variable is not scoped to the enclosing scope.

@gafter
Copy link
Member Author

gafter commented Oct 22, 2015

If you want the variable to be in scope in the enclosing block, then you declare it in the enclosing block.

@leppie
Copy link
Contributor

leppie commented Oct 22, 2015

@gafter:

So in the first statement both div and mod will not be accessible, but still compile? If so, would the following (non-sensical statement) be allowed?

DivMod(num, den, out var _, out var _); 

And in the 2nd statement, it would be a compiler error?

@gafter
Copy link
Member Author

gafter commented Oct 22, 2015

You cannot use the same name (_) to declare two different variables in the same scope. But there is no problem with your example Foo(Bar(out var baz), baz); as the scope of baz is the enclosing statement.

@AdamSpeight2008
Copy link
Contributor

#11566
Can you clarify this?

  • There is a proposal pending LDM decision: An out variable may not be referenced before the close parenthesis of the invocation in which it is defined:

The out variable can not be referred to, within the other parameters of the method.

    M(out x, x = 3, 0); // error

Is the proposal the equivalent to the following?

M( out x, x = 3)
// Equivalent to 
int x; 
M( out x , x = 3 )

If so, then to referred to restriction, isn't a restriction.


Is the **our variableaccessible with then other branches of theif`?

If M(out x, "FOO" ) Then
' ...
Else If m = 0 Then
 ' ...
Else
  Console.Write(x)
End If

@AlekseyTs
Copy link
Contributor

Yes, the proposal would make this

M(out int x, x = 3, 0); // error

or this

M1(out int x, M2(M3(out x), x))

an error.

The tentative plan is to use the same scoping rules as for pattern variables, which could be found here https://github.com/dotnet/roslyn/blob/features/outvar/docs/features/patterns.md

@AdamSpeight2008
Copy link
Contributor

If the out variable is gets defined into a scope local to the statement / expression.
Does that mean it overloads one that is already defined?
Or would that also be an error?

@Thaina
Copy link

Thaina commented Jun 8, 2016

Is it possible to use only var at out var

Because if we just use var at function parameter it should be obvious that it is used for out so we could drop out and just use var instead of full out var

so

int.TryParse("10",var i);

Also I would like to have ref var like this

void RefParse(string number,ref int i)
{
    int n;
    if(int.TryParse(number,out n))    // i would be default value if string cannot be parsed
        i = n;
}

// `ref var` difference from `out var` by = symbol
RefParse("10",var i = 0);

@phrohdoh
Copy link
Contributor

phrohdoh commented Jun 8, 2016

But RefParse doesn't have an out parameter so using var there doesn't make sense even with your example. Right?

@Thaina
Copy link

Thaina commented Jun 8, 2016

@phrohdoh That's what I said // 'ref var' difference from 'out var' by = symbol so both use the same var but of ref you need to declare value while out is not

@ghost
Copy link

ghost commented Aug 23, 2016

@gafter, a little off-topic set of questions about the syntax you wrote above:

o is BazNode node

is this a valid C# syntax? since which version? Is it equivalent to:

BazNode node = null;
if(o is BasNode) node = new BazNode()

?

What if when BazNode has no ctor accepting zero params, would that magical syntax fail?

@Thaina
Copy link

Thaina commented Aug 23, 2016

@jasonwilliams200OK No and No.

It is not valid yet. They are some people here proposed to let it valid in next version (but I am on against faction)

And what it is doing is more like this

if(o is BasNode)
{
   BazNode node = o as BasNode;
   // use node here
}

The purpose is to use object in if scope as short as possible

@AdamSpeight2008
Copy link
Contributor

@jasonwilliams200OK Think of if as introducing the variable into scope, with value as if it was of that type.

@Opiumtm
Copy link

Opiumtm commented Sep 23, 2016

As upcoming feature "pattern matching" is allowing * pattern (match anything), it would be applicable to use same literal for out parameter that isn't interesting and should be ignored.

Example:

Before:

int a;
int b; // variable b isn't used but declared only to store out-result

SomeMethod(out a, out b);
return a;

After:

int a;
SomeMethod(out a, out *);  // "out *" is indicating that second out parameter isn't used.

// another example - we are simply testing variable "s" can be parsed to integer, out parameter value
// is ignored
var s = "12";
if (int.TryParse(s, out *) 
{
    Console.WriteLine($"{s} is integer");
}

@HaloFour
Copy link

@Opiumtm

That's planned, although it may not make C# 7.0. Could be in a minor release, though:

https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

@BruceWilliams
Copy link

I just saw this feature mentioned on an MSDN blog, and my first thought when I saw this:
p.GetCoordinates(out var x, out var y);
Was - why type the 'var'? Would there be some additional ambiguity or something if we just accepted:
p.GetCoordinates(out x, out y);
To mean the same thing?

@HaloFour
Copy link

HaloFour commented Oct 5, 2016

@BruceWilliams

The syntax out <identifier> is already legal syntax. If the compiler were changed so that it would create a new variable if one wasn't already in scope that could lead to issues where a typo would result in accidentally create a new out declaration variable than populate the existing variable.

Also, the syntax is really out <type> <identifier>. var may be used in place of the type where there is no ambiguity, but since methods can overload on out parameters if the type was implied it would never be possible to resolve that ambiguity:

void Foo(out int x) { ... }
void Foo(out string x) { ... }

Foo(out x); // is x an int or a string?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests