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: Go 2: add "var name auto" to handle some edge cases in type inference #46109

Closed
earthboundkid opened this issue May 11, 2021 · 16 comments
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal Proposal-FinalCommentPeriod v2 An incompatible library change
Milestone

Comments

@earthboundkid
Copy link
Contributor

earthboundkid commented May 11, 2021

Would you consider yourself a novice, intermediate, or experienced Go programmer?

Experienced.

What other languages do you have experience with?

Python, JavaScript, scattered others.

Would this change make Go easier or harder to learn, and why?

Marginally harder but it's an advanced feature users should only rarely encounter.

Has this idea, or one like it, been proposed before?

Not AFAIK, but it has a parallel in C/C++.

Who does this proposal help, and why?

It prevents type repetition in some cases and fills an inconsistency in the type system in others.

What is the proposed change?

As a special case, if a variable is declared with type auto, it will take infer type from the subsequent first assignment, as per the rules of :=.

For example in this case, f will infer a type of func(int, int) int:

var f auto
f = func(a, b int) int { return a + b }

This is particularly helpful in cases of recursive closures (see #33167), which currently must be written like this if they aren't at the top level:

var fac func(int, int) int
fac = func(n, acc int) int {
  if n <= 1 { return 1 }
  return n * fac(n-1)
}

This leads to an inconvenient repetition of the type information.

With auto, the first line would be var fac auto and the rest would be the same.

Another case in which there is an inconvenient repetition is this:

v1, err := doSomething()
if cond(v1) {
  var v2 T
  v2, err = doSomethingElse()
  // ...
}

Essentially, the programmer is faced with the unfortunate dilemma of either shadowing err or redeclaring v2. Unfortunately, in some cases, redeclaring v2 is impossible because the type of v2 is in another unexported package.

Please describe as precisely as possible the change to the language.

auto would be a predeclared identifier (or possibly a keyword at the risk of breaking existing packages) that, if not otherwise overwritten in function local scope, the compiler would interpret as an instruction to infer the type from a subsequent first assignment. Accessing an unassigned auto variable would be a compiler error. Like := short declarations, auto would only be legal inside of a function/method, because first assignment is not well defined at the package level.

var x auto
doSomething(x) // illegal use before assignment
x = firstAssignment()

What would change in the language spec?

Assuming it is added a magic declared identifier, the grammar for the language could remain the same. There would be a new section between "Short variable declarations" and "Function declarations" explaining that var x auto inside of a function has rules similar to :=

Please also describe the change informally, as in a class teaching Go.

Sometimes you want to use := for its type inference but only create new variables for some of the types. For those scenarios, you can use the magic type auto with var like:

// x already exists
var y, z auto
x, y, z = XYZer()

Is this change backward compatible?

I believe so if implemented as a magic predeclared identifier.

Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit.

Show example code before and after the change.

See above.

What is the cost of this proposal? (Every language change has a cost).

  • Existing tools that do type inference will need to be updated.
  • New education materials.

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?

I think that tools that don't do type analysis like gofmt and goimports could be left as is.

What is the compile time cost?

Negligible.

What is the run time cost?

None.

Can you describe a possible implementation?

I am not familiar enough with the go tool internals, but I assume it can be done by reusing the type inference in use for := plus some new code for the magic predeclared identifier.

Do you have a prototype? (This is not required.)

No.

How would the language spec change?

See above.

Orthogonality: how does this change interact or overlap with existing features?

It overlaps with := and makes proposals to remove variable shadowing more palatable.

Is the goal of this change a performance improvement?

No.

If so, what quantifiable improvement should we expect?

How would we measure it?

Does this affect error handling?

It affects the case := shadowing err by making it more palatable to write:

var a auto
a, err := somethingWithErr()

It also makes this possible:

var a auto
if a, err := doSomething(); err != nil {
  // a is in the outer scope
  // err is in scope here
}
// a is in scope
// err is out of scope

If so, how does this differ from previous error handling proposals?

It is not a comprehensive solution to error problems.

Is this about generics?

No.

@gopherbot gopherbot added this to the Proposal milestone May 11, 2021
@seankhliao seankhliao changed the title proposal: add "var name auto" to handle some edge cases in type inference proposal: Go 2: add "var name auto" to handle some edge cases in type inference May 11, 2021
@seankhliao seankhliao added v2 An incompatible library change LanguageChange Suggested changes to the Go language labels May 11, 2021
@randall77
Copy link
Contributor

What is "first assignment", exactly?

var x auto
if ... {
    x = f()
} else {
    x = g()
}

Does x get the return type of f or of g? Or is it an error? Or is it an error only if the types of f and g differ?
What if there is no else branch, and the if is not taken. Does x have the return type of f after the if statement? Or only inside the if statement after the assignment?

@robpike
Copy link
Contributor

robpike commented May 11, 2021

One of the things we didn't get right with the design of Go is that that there are too many different ways to declare a variable. Adding another way is not the solution.

@earthboundkid
Copy link
Contributor Author

What is "first assignment", exactly?

var x auto
if ... {
    x = f()
} else {
    x = g()
}

The simple answer is x has the type of the lexically first assignment.

It’s already an error if you use a short declaration and the return types of f and g are incompatible. The interesting question is what if they are assignable but different types like an interface and a concrete. But that already occurs with short declarations and the lexically first rule is simple to understand and implement.

@earthboundkid
Copy link
Contributor Author

One of the things we didn't get right with the design of Go is that that there are too many different ways to declare a variable. Adding another way is not the solution.

I figured this was a long shot proposal but #33167 has been nagging me for a while so I might as well get the potential solution out there.

The simple solution to the too many declaration types problem is to remove :=. 😄 If Go were a new language, I think dropping := in favor of var x auto would be a good call. For Go being what it is, uh, it’s probably a bridge too far, but it’s interesting to consider.

@zigo101
Copy link

zigo101 commented May 11, 2021

Maybe simply var x is okay?

@zigo101
Copy link

zigo101 commented May 12, 2021

The simple solution to the too many declaration types problem is to remove :=. ...

Personally, I think enforcing re-declared variables represented with a non-pure identifier form is good enough to avoid many confusions. go fix and go fmt could help us rewrite history and current code.

@deanveloper
Copy link

I do not like this. Especially in larger functions, there can be large gaps between variable declarations and when they are first used. This makes it extremely unclear what type a variable will hold. The type of a variable should always be able to be easily found where it is declared.

@zigo101
Copy link

zigo101 commented May 20, 2021

For the

var f auto
f = func(a, b int) int { return a + b }

case, the type of f is clearly shown nearby.

The type of a variable should always be able to be easily found where it is declared

This is not very true. Otherwise, type deduction should not be supported.

@deanveloper
Copy link

@go101 As I had said, I was talking about cases where the space between variable declaration and first assignment is larger. For instance, variables that need to be declared in scope of the whole function, but the first use of the variable is in a conditional inside of a nested for-loop:

var found auto
var twoDimSlice [][]int = ...;
outerLoop:
for y, eachSlice := range twoDimSlice {
    for x, eachValue := range eachSlice {
        if someCondition(eachValue) {
            found = x
        }
    }
}

Also - it is true that the type of a variable can always be easily found, Type deduction works by using the type of the assigned value. The type of the assigned value can always be easily found by looking upwards in the function.

However, with auto, I would first need to look downwards in the function for the first assignment, and then I would have to look back upwards to find out what type is being assigned to it. (In my example, it’s clear what type is being assigned once the first assignment is found. However, this would be much less clear if it is being assigned a value that was declared much earlier in the function!)

@zigo101
Copy link

zigo101 commented May 20, 2021

However, with auto, I would first need to look downwards in the function for the first assignment, ...

Why would you concern this? After all, between the declaration and the first assignment, the variable is not used at all.

@deanveloper
Copy link

As my example shows, in order to figure out the type of an auto variable, you must:

  1. know the location of the first assignment
  2. figure out the type of the expression that it is being assigned to

this involves searching downward for the assignment, then back upward to figure out the type of the expression. in my example, this is illustrated by searching for the first assignment of found (being assigned to x) then search for the declaration of x to find its type. This would be even worse if x were also auto-typed, as you would have to repeat this process again.

@deanveloper
Copy link

I should also point out - what do we do with auto types when they are passed into something like Scanf as their only operation?

@zigo101
Copy link

zigo101 commented May 20, 2021

As my example shows, in order to figure out the type of an auto variable, you must:

1. know the location of the first assignment
2. figure out the type of the expression that it is being assigned to

this involves searching downward for the assignment, then back upward to figure out the type of the > expression. in my example, this is illustrated by searching for the first assignment of found (being > assigned to x) then search for the declaration of x to find its type. This would be even worse if x were also > auto-typed, as you would have to repeat this process again.

I think here only the first point is valid. For the others also exist for current already supported typed deducted variables. And I think the first point is not serious in most cases.

I should also point out - what do we do with auto types when they are passed into something like Scanf as their only operation?

The OP mentioned this above:

var x auto
doSomething(x) // illegal use before assignment
x = firstAssignment()

Personally, I think the auto declaration and its first assignments should share the same innermost containing code block.

@earthboundkid
Copy link
Contributor Author

Something like this is a little tricky to think through, but I think banning it would be more trouble than it's worth:

var x auto
if cond {
   x = something()
}
fmt.Println("used x?", x != zerovalue)

Logically, it's not worse than just var x sometype, it's just that the type information is introduced in the conditional block.

var x auto
if cond {
   x = 1
} else {
   x = "two"
}

In this case, I think the compiler should say that x has type int from the first assignment, so x = "two" is an illegal assignment of a string to an int.

@ianlancetaylor
Copy link
Member

The discussion above points out various difficulties with this proposal. The emoji voting does not show support. Therefore, this is a likely decline. Leaving open for four weeks for final comments.

@ianlancetaylor
Copy link
Member

No further comments.

@golang golang locked and limited conversation to collaborators Aug 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal Proposal-FinalCommentPeriod v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

8 participants