Goes without saying: this is an opinion piece. If we disagree, that's cool!
This is just what I consider relatively easy to remove. I have other complaints about Go, mostly related to its deep fundamentals that would be very hard or impossible to change. They're not mentioned in this piece.
We're not allowed to break existing code under Go1. However, it seems plausible to migrate most existing code in advance, preparing it for the hypothetical Go2 that removes the deprecated features, alongside other breaking changes it's expected to make. The following migration strategy seems realistic:
go fix
converts existing code to the "new" style, avoiding the "deprecated" features:=
in favor of var
Remove iota
Maybe remove new
in favor of &
Remove :=
in favor of var
1. Having two equivalent assignment forms is redundant.
2. :=
can't justify itself with brevity. Compared to var
, it requires one or two fewer keystrokes to type, but involves Shift
and an awkward movement between :
and =
. Subjectively, I find var
easier and faster to type.
3. Code sometimes needs to be converted between :=
, var
and const
. For example, you have a string that's initially produced by fmt.Sprintf
, but as you edit the code, it becomes a const
. Or vice versa. I find these conversions fiddly and awkward. Converting between var
and const
is noticeably easier.
Moving a declaration between local and global scopes also involves converting between :=
and var
. This should be unnecessary.
4. Some idiomatic code already prefers var
. For example, it's commonly used for zero values:
var buf bytes.Buffer buf.WriteString("hello world!") _ = buf.Bytes()
5. As shown above, var
allows to specify the type. Type inference is nice, but sometimes you have to spell it out:
num := 10 num := float64(10) var num float64 = 10 var num = float64(10)
Without :=
, you'd have less choice, which is good.
6. var
also allows the blank identifier:
var _ = 123 // compiles _ := 123 // doesn't compile
7. var
is also better for code highlighting. While writing a Go syntax definition for Sublime Text, I found that it's impossible to correctly scope the following:
one, two := someExpression
Scoping the variable names as declarations with :=
requires multiline lookahead or backtracking, neither of which is supported in the modern Sublime Text syntax engine.
With var
, this can be properly scoped without multiline lookahead or backtracking:
var one, two = someExpression
Completely embracing var
requires an addition to the language. Various forms of if
, for
, select
, and switch
currently support :=
but not var
:
// compiles ok select { case err := <-errChan: case msg := <-msgChan: } // doesn't compile select { case var err = <-errChan: case var msg = <-msgChan: }
For Go1, adding the missing var
support would be a safe, backwards-compatible change.
See the related . gofmt change
var
, const
, type
, import
Let's start with arguments in favor of the feature.
Currently, parenthesized lists have exactly one non-aesthetic reason to exist: const (...)
enables the use of iota
, acting as its scope.
import
is traditionally listed, so the keyword doesn't repeat:
import ( "bytes" "encoding" "encoding/base64" )
import "bytes" import "encoding" import "encoding/base64"
That's a weak-ass justification for an entire language feature, made even weaker by goimports which edits your imports
automatically.
Now, arguments against the feature.
Code should be convenient to type and edit. I think having options hinders that. Every time you write adjacent vars, some of your neurons are wasted on choosing between:
var one = _ var two = _
and:
var ( one = _ two = _ )
Worse, it occasionally leads to menial conversions between the two. That's a waste of brainpower and typing. Let's say you have a single var:
const one = 10
Now you're adding another:
const one = 10 const two = 20
You might be compelled to convert to the list style:
const ( one = 10 two = 20 )
We've now wasted some brainpower and typing. Without lists, this would not have happened.
For consistency, the go.mod
syntax should also remove lists.
iota
due to removing listsiota
requires parenthesized const (...)
for scoping. Removing lists also leads to removing iota
.
While I tend to avoid iota
, I don't have a strong argument against it. If keeping iota
in the language is important, then instead of removing lists entirely, we could just consider them non-idiomatic unless iota
is used.
new
in favor of &
new
was relevant when &
was allowed only on "storage locations" such as variables and inner fields. Now that &
is allowed on composite literals, new
is close to obsolete.
new
is limited to a zero value, while &
allows content:
client := new(http.Client) client.Timeout = time.Minute client = &http.Client{Timeout: time.Minute}
Currently, &
doesn't work with non-composite literals:
// doesn't compile _ = &"hello world!"
Before new
can be removed, &
needs to be extended to support primitive literals. That would make it strictly more powerful than new
. (Edit 2020-10-19: some types, such as interfaces, don't have literals and can never be instantiated with &
, but can with new
.)
Allowing &
on primitives would also make it easier to print Go data structures as code. Currently, pretty-printing libraries have to resort to ugly workarounds to support those types.
Note that most code can already be converted to &
. Code like new(string)
or new(int)
should be rare in the wild.
For Go1, extending &
to primitive literals would be a safe, backwards-compatible change.
import . "some-package"
Dot-import splurges all exported definitions from another package into the current scope:
import . "fmt" func main() { Println("hello world!") }
Having read a considerable amount of code in multiple languages with this import style, I'm convinced that it's always a bad idea. Subjectively, it makes the code harder to understand and harder to track down the definitions. Objectively, it makes the code more fragile against changes.
if _ := _ ; _ {}
Subjectively, I find this form annoying to type and annoying to read. Objectively, it's a choice, and this post is predicated on "choice is bad". This wastes everyone's brainpower; anyone reading the code has to be aware of both syntactic forms.
Instead of two options:
if ok := _; ok { _ } ok := _ if ok { _ }
Let's leave just one option:
var ok = _ if ok { _ }
If subscoping the variable is vital, just use a block. This also allows you to subscope more than one variable.
{ var ok = _ if ok { _ } }
(This entry was added on 2020-06-11.)
In Go, the following forms are equivalent:
var _ = 0.123 var _ = .123
The short form works only for numbers below 0
and is not essential. The long form is essential and more general. Subjectively, I find the short form slightly harder to read; my brain starts thinking about typos and other syntactic forms involving dots. Objectively, it creates an unnecessary choice. Let's leave just one option: the "long" form.
var
, const
, type
, import
Currently, gofmt
aligns adjacent assignments only in parenthesized lists:
const ( one = 10 two = 20 three = 30 ) const one = 10 const two = 20 const three = 30
After , we probably want removing parenthesized listsgofmt
to align adjacent non-parenthesized assignments:
const one = 10 const two = 20 const three = 30
While writing this post, I tried to argue that complex numbers should be moved from built-ins to the standard library, but ended unconvinced.
Arguments for moving:
Arguments against moving:
math
strconv
, fmt
, encoding/json
, encoding/xml
, etc.In the end, I'm not convinced that it's worthwhile.
Have any thoughts? Let me know!
This blog currently doesn't support comments. Write to me via contacts.