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: spec: make fewer types nillable #28133

Open
zenhack opened this Issue Oct 10, 2018 · 17 comments

Comments

Projects
None yet
10 participants
@zenhack
Copy link

zenhack commented Oct 10, 2018

Apologies if this has been suggested before; I've been unable to find an issue.

Currently go has many basic types where the zero value is nil. nil is a huge source of bugs, and a potential Go 2, where backwards-incompatible changes are on the table, offers an opportunity to make the language more robust by reducing its scope.

Specifically, in some cases there is an obvious zero-value that makes more sense than nil. Off the top of my head:

  • The zero value for slices could be a slice with length and capacity of zero.
  • The zero value for maps could be an empty map.
  • The zero value for channels could be an unbuffered channel.

This is more in keeping with the design principle of making the zero value useful.

There isn't an obvious good zero value that I can think of for pointers and interfaces, but even this partial reduction would make it easier to build reliable software.

@bcmills bcmills changed the title Go 2: make fewer types nillable proposal: Go 2: spec: make fewer types nillable Oct 10, 2018

@gopherbot gopherbot added this to the Proposal milestone Oct 10, 2018

@gopherbot gopherbot added the Proposal label Oct 10, 2018

@bcmills

This comment has been minimized.

Copy link
Member

bcmills commented Oct 10, 2018

nil slices and channels are, in my experience, not usually a significant problem.

nil maps and nil pointers, on the other hand...

@bcmills bcmills modified the milestones: Proposal, Go2 Oct 10, 2018

@zenhack

This comment has been minimized.

Copy link

zenhack commented Oct 10, 2018

I've definitely run into bugs from nil slices (less so channels, but it has happened, and it seems like a straightforward fix). Agree that maps and (especially) pointers are a much bigger problem. Killing nil pointers would be huge but I don't know how to do it while still having zero values.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Oct 10, 2018

Related: #22729

@magical

This comment has been minimized.

Copy link
Contributor

magical commented Oct 10, 2018

It's worth noting that nil channels have useful properties in select statements: sending or receiving on a nil channel always blocks, so nil-ing out a channel before doing a select effectively removes cases involving that channel from consideration, allowing one to dynamically enable or disable cases at runtime.

That said, it's definitely annoying to have to explicitly initialize channels.

@deanveloper

This comment has been minimized.

Copy link

deanveloper commented Oct 10, 2018

The zero value for slices could be a slice with length and capacity of zero.

That's already what it does :) A nil slice has no behavior difference from a zero-length-zero-capacity slice, except that s == nil is true.

I love this though. Zero values should be useful, begone with nil for things that don't need it!

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Oct 10, 2018

One of the advantages of the current approach is that the zero values for all types is the value with all bits zero. This makes it easy to initialize variables. Of course that is not an absolute requirement; we could change that with some loss of efficiency. But it's the main reason that nil channels are not useful.

@robpike

This comment has been minimized.

Copy link
Contributor

robpike commented Oct 10, 2018

But nil channels are useful: They signal that the communication cannot happen and thereby provide the ability to control individual cases in select statements.

@zenhack

This comment has been minimized.

Copy link

zenhack commented Oct 10, 2018

I don't feel as strongly about channels, and apparently I was just plain mistaken re: slices (though it would probably be more intuitive for them to be non-nillable; most of the code I've seen in the wild that cares about nil slices seems to be written under the assumption that the same problems crop up as with pointers).

Making maps non-nillable seems like it is a very big win for robustness though.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Oct 10, 2018

Currently map is a covert pointer, whereas slice is a covert struct.

A Go2 default-empty map probably shouldn't allocate a map object until necessary, but then every m[k]=v incurs a test. Today every append(s, v) incurs a test, so maybe that's fine?

@zenhack

This comment has been minimized.

Copy link

zenhack commented Oct 10, 2018

m[k]=v already has to do a nil check, but right now it panics instead of initializing the map.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Oct 10, 2018

Note that m[k] = v can not initialize the map if it is nil. Because maps have reference semantics, if we are going to initialize them automatically, we must do so at the point of declaration. Consider

func Add(m map[int]int) { m[0] = 1 }
func F() {
    var m map[int]int
    Add(m)
    fmt.Println(m[0])
}
@zenhack

This comment has been minimized.

Copy link

zenhack commented Oct 10, 2018

@networkimprov, that doesn't fix the problem; if it's an unboxed struct and you assign it, then use one copy or the other, they end up being different maps, since initializing one pointer doesn't initialize the other.

Doing up-front allocation would at least have the upside of allowing the nil check to always be omitted. Probably still a loss overall wrt perf.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Oct 10, 2018

Sorry, just deleted the comment you refer to (re making map a covert struct like slice), as I realized that slice has the same issue @ianlancetaylor described :-)

However that code works if map is a covert **MapType. But that incurs an extra dereference :-(

@zenhack

This comment has been minimized.

Copy link

zenhack commented Oct 10, 2018

But then you still have to allocate the (outermost) pointer up front.

@networkimprov

This comment has been minimized.

Copy link

networkimprov commented Oct 11, 2018

Given all that, and the need for reference semantics, maybe default-empty maps don't fly.

@bronze1man

This comment has been minimized.

Copy link

bronze1man commented Oct 12, 2018

@neganovalexey
I think slice is not a reference ,but map is a reference is also a source of bug.
How about add another map like smap which is not a reference, and make it default-empty, and leave the origin map as it is.
This way may even save some allocs in some cases.

@wsc1

This comment has been minimized.

Copy link

wsc1 commented Oct 19, 2018

I think the most important thing to keep in mind for any changes to nil-able things is consistency.

One thing that is consistent about the current semantics is that all nil-able (atomic) things are in some sense a reference or pointer and vice versa: all reference or pointer (atomic) things are nil-able. If you start making exceptions to that, then there are more rules to keep in mind and more clutter of specifications.

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