Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save BrianWill/671ce51e6ef6a9f0caa27272a9a0637f to your computer and use it in GitHub Desktop.
Save BrianWill/671ce51e6ef6a9f0caa27272a9a0637f to your computer and use it in GitHub Desktop.

Problem

The := syntax is error-prone when dealing with multiple targets and variables of enclosing scope:

// often a mistake:
var a int
{
  a, b := foo()    // creates a new 'a' in this scope instead of using 'a' of outer scope
}

// the fix
var a int
{
  var b *Bar
  a, b = foo()
}

Not only is the fix verbose, it defeats a nicety of inferred typing that it often spares us from having to remember precise return type(s), e.g. whether a returned type is a pointer or not:

a := bar()       // whether bar() returns an *X or just an X, we often don't need to care

Normally, Go disallows redeclarations, but it makes a hacky exception for the := syntax, such that we can't look at an := assignment and know for sure which of the target variables are being declared:

x, y, z := foo()    // only one of x, y, and z must be new, but just looking here doesn't tell us which variables are new

Another problem is that we can't mix 'name' targets and 'non-name' targets:

a, b[0] := foo()          // compile error: b[0] is a non-name target

Proposal

Instead of marking an assignment itself as an implicit declaration, mark the individual target variables. Let's say for now we'll use ' as a suffix marker:

x', y, z' = foo()         // assign to new variable 'x', existing variable 'y', and new variable 'z'

With this syntax, I don't see a good reason to disallow mixing in non-name targets:

a', b[0] = foo()          // ok

It would also be nice if we could optionally specify types of one or more of the newly declared target variables to make them different from the corresponding return type:

a' Fruit, b = bar()       // first return type of bar() is Banana, but we store it in a Fruit interface variable

Lastly, while it's great that Go requires explicit casts in most cases, it would sometimes be nice to cast the individual return values in a multi-assignment. In fact, it would sometimes be nice if we could perform any arbitrary operation on each return value. A solution would be to allow an expression after each target where _ stands in for the return value:

// the first return value is cast to int64 and the result assigned to new variable 'a'
// the second return value is added to 3 and the result assigned to existing variable 'b'
a' int64(_), b _ + 3 = foo()

If arbitrary expressions in these positions make the code too cluttered, perhaps restrict the expressions to being just single function/method calls.

Type-independent zero value literal

Another small thing I'd like in Go is a literal that stands-in for any zero value. Let's say # is this special literal:

var f Foo     // struct type Foo
f = #         // f = Foo{}

This literal would be particularly handy when returning zero values along with a non-nil error. Instead of having to write specific zero values...

return SomeStruct{}, "", err

...we could just write:

return #, #, err
@BrianWill
Copy link
Author

@alejoloaiza Yeah, I'm not proposing getting rid of any syntax, just adding new stuff. I think these additions are compatible with the current grammar, but if not, I'm not married to these particular symbols.

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